Bug 1473736 - Implement necko part of ESNI r=mak,kmag,mcmanus
authorDragana Damjanovic <dd.mozilla@gmail.com>
Sat, 22 Sep 2018 23:54:11 +0300
changeset 493547 52ee9ddf50602cea9c21dd3855c376f62232a289
parent 493546 83ab7c05f538b264907a99800a68892477f34a01
child 493548 64e0354439554bd928cdd999823d5e3b6e911a3b
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak, kmag, mcmanus
bugs1473736
milestone64.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 1473736 - Implement necko part of ESNI r=mak,kmag,mcmanus
browser/base/content/browser.js
dom/base/nsContentSink.cpp
dom/html/nsHTMLDNSPrefetch.cpp
dom/html/nsHTMLDNSPrefetch.h
media/mtransport/nriceresolver.h
modules/libpref/init/all.js
netwerk/base/Dashboard.cpp
netwerk/base/Predictor.cpp
netwerk/base/ProxyAutoConfig.cpp
netwerk/base/nsDNSPrefetch.cpp
netwerk/base/nsDNSPrefetch.h
netwerk/base/nsISocketTransport.idl
netwerk/base/nsSocketTransport2.cpp
netwerk/base/nsSocketTransport2.h
netwerk/base/nsSocketTransportService2.cpp
netwerk/base/nsSocketTransportService2.h
netwerk/base/nsUDPSocket.cpp
netwerk/dns/ChildDNSService.cpp
netwerk/dns/ChildDNSService.h
netwerk/dns/DNSListenerProxy.cpp
netwerk/dns/DNSListenerProxy.h
netwerk/dns/DNSRequestChild.cpp
netwerk/dns/DNSRequestChild.h
netwerk/dns/DNSRequestParent.cpp
netwerk/dns/DNSRequestParent.h
netwerk/dns/PDNSRequest.ipdl
netwerk/dns/PDNSRequestParams.ipdlh
netwerk/dns/TRR.cpp
netwerk/dns/TRR.h
netwerk/dns/TRRService.cpp
netwerk/dns/TRRService.h
netwerk/dns/moz.build
netwerk/dns/nsDNSService2.cpp
netwerk/dns/nsDNSService2.h
netwerk/dns/nsHostResolver.cpp
netwerk/dns/nsHostResolver.h
netwerk/dns/nsIDNSByTypeRecord.idl
netwerk/dns/nsIDNSListener.idl
netwerk/dns/nsIDNSService.idl
netwerk/ipc/NeckoParent.cpp
netwerk/ipc/NeckoParent.h
netwerk/ipc/PNecko.ipdl
netwerk/protocol/http/TunnelUtils.cpp
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnectionInfo.cpp
netwerk/protocol/http/nsHttpConnectionInfo.h
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/websocket/WebSocketChannel.cpp
netwerk/socket/nsISSLSocketControl.idl
netwerk/socket/nsSOCKSIOLayer.cpp
netwerk/test/unit/test_esni_dns_fetch.js
netwerk/test/unit/xpcshell.ini
security/manager/ssl/nsNSSIOLayer.cpp
security/manager/ssl/nsNSSIOLayer.h
testing/xpcshell/moz-http2/moz-http2.js
toolkit/components/extensions/parent/ext-dns.js
toolkit/components/telemetry/Histograms.json
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -899,69 +899,71 @@ function gKeywordURIFixup({ target: brow
   // but still work to access IP addresses. So for instance,
   // 1097347366913 (ff7f000001) gets resolved by using the final bytes,
   // making it the same as 7f000001, which is 127.0.0.1 aka localhost.
   // While 2130706433 would get normalized by network, 1097347366913
   // does not, and we have to deal with both cases here:
   if (isIPv4Address(asciiHost) || /^(?:\d+|0x[a-f0-9]+)$/i.test(asciiHost))
     return;
 
-  let onLookupComplete = (request, record, status) => {
-    let browserRef = weakBrowser.get();
-    if (!Components.isSuccessCode(status) || !browserRef)
-      return;
-
-    let currentURI = browserRef.currentURI;
-    // If we're in case (3) (see above), don't show an info bar.
-    if (!currentURI.equals(previousURI) &&
-        !currentURI.equals(preferredURI)) {
-      return;
-    }
-
-    // show infobar offering to visit the host
-    let notificationBox = gBrowser.getNotificationBox(browserRef);
-    if (notificationBox.getNotificationWithValue("keyword-uri-fixup"))
-      return;
-
-    let message = gNavigatorBundle.getFormattedString(
-      "keywordURIFixup.message", [hostName]);
-    let yesMessage = gNavigatorBundle.getFormattedString(
-      "keywordURIFixup.goTo", [hostName]);
-
-    let buttons = [
-      {
-        label: yesMessage,
-        accessKey: gNavigatorBundle.getString("keywordURIFixup.goTo.accesskey"),
-        callback() {
-          // Do not set this preference while in private browsing.
-          if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
-            let pref = "browser.fixup.domainwhitelist." + asciiHost;
-            Services.prefs.setBoolPref(pref, true);
-          }
-          openTrustedLinkIn(alternativeURI.spec, "current");
+  let onLookupCompleteListener = {
+    onLookupComplete(request, record, status) {
+      let browserRef = weakBrowser.get();
+      if (!Components.isSuccessCode(status) || !browserRef)
+        return;
+
+      let currentURI = browserRef.currentURI;
+      // If we're in case (3) (see above), don't show an info bar.
+      if (!currentURI.equals(previousURI) &&
+          !currentURI.equals(preferredURI)) {
+        return;
+      }
+
+      // show infobar offering to visit the host
+      let notificationBox = gBrowser.getNotificationBox(browserRef);
+      if (notificationBox.getNotificationWithValue("keyword-uri-fixup"))
+        return;
+
+      let message = gNavigatorBundle.getFormattedString(
+        "keywordURIFixup.message", [hostName]);
+      let yesMessage = gNavigatorBundle.getFormattedString(
+        "keywordURIFixup.goTo", [hostName]);
+
+      let buttons = [
+        {
+          label: yesMessage,
+          accessKey: gNavigatorBundle.getString("keywordURIFixup.goTo.accesskey"),
+          callback() {
+            // Do not set this preference while in private browsing.
+            if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
+              let pref = "browser.fixup.domainwhitelist." + asciiHost;
+              Services.prefs.setBoolPref(pref, true);
+            }
+            openTrustedLinkIn(alternativeURI.spec, "current");
+          },
         },
-      },
-      {
-        label: gNavigatorBundle.getString("keywordURIFixup.dismiss"),
-        accessKey: gNavigatorBundle.getString("keywordURIFixup.dismiss.accesskey"),
-        callback() {
-          let notification = notificationBox.getNotificationWithValue("keyword-uri-fixup");
-          notificationBox.removeNotification(notification, true);
+        {
+          label: gNavigatorBundle.getString("keywordURIFixup.dismiss"),
+          accessKey: gNavigatorBundle.getString("keywordURIFixup.dismiss.accesskey"),
+          callback() {
+            let notification = notificationBox.getNotificationWithValue("keyword-uri-fixup");
+            notificationBox.removeNotification(notification, true);
+          },
         },
-      },
-    ];
-    let notification =
-      notificationBox.appendNotification(message, "keyword-uri-fixup", null,
-                                         notificationBox.PRIORITY_INFO_HIGH,
-                                         buttons);
-    notification.persistence = 1;
+      ];
+      let notification =
+        notificationBox.appendNotification(message, "keyword-uri-fixup", null,
+                                           notificationBox.PRIORITY_INFO_HIGH,
+                                           buttons);
+      notification.persistence = 1;
+    },
   };
 
   try {
-    gDNSService.asyncResolve(hostName, 0, onLookupComplete, Services.tm.mainThread,
+    gDNSService.asyncResolve(hostName, 0, onLookupCompleteListener, Services.tm.mainThread,
                              contentPrincipal.originAttributes);
   } catch (ex) {
     // Do nothing if the URL is invalid (we don't want to show a notification in that case).
     if (ex.result != Cr.NS_ERROR_UNKNOWN_HOST) {
       // ... otherwise, report:
       Cu.reportError(ex);
     }
   }
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -903,16 +903,17 @@ nsContentSink::PrefetchPreloadHref(const
     }
   }
 }
 
 void
 nsContentSink::PrefetchDNS(const nsAString &aHref)
 {
   nsAutoString hostname;
+  bool isHttps = false;
 
   if (StringBeginsWith(aHref, NS_LITERAL_STRING("//")))  {
     hostname = Substring(aHref, 2);
   }
   else {
     nsCOMPtr<nsIURI> uri;
     NS_NewURI(getter_AddRefs(uri), aHref);
     if (!uri) {
@@ -922,20 +923,21 @@ nsContentSink::PrefetchDNS(const nsAStri
     bool isLocalResource = false;
     rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
                              &isLocalResource);
     if (NS_SUCCEEDED(rv) && !isLocalResource) {
       nsAutoCString host;
       uri->GetHost(host);
       CopyUTF8toUTF16(host, hostname);
     }
+    uri->SchemeIs("https", &isHttps);
   }
 
   if (!hostname.IsEmpty() && nsHTMLDNSPrefetch::IsAllowed(mDocument)) {
-    nsHTMLDNSPrefetch::PrefetchLow(hostname,
+    nsHTMLDNSPrefetch::PrefetchLow(hostname, isHttps,
                                    mDocument->NodePrincipal()->OriginAttributesRef());
   }
 }
 
 void
 nsContentSink::Preconnect(const nsAString& aHref, const nsAString& aCrossOrigin)
 {
   // construct URI using document charset
--- a/dom/html/nsHTMLDNSPrefetch.cpp
+++ b/dom/html/nsHTMLDNSPrefetch.cpp
@@ -38,16 +38,17 @@ using namespace mozilla::dom;
 using namespace mozilla::net;
 
 static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID);
 bool sDisablePrefetchHTTPSPref;
 static bool sInitialized = false;
 static nsIDNSService *sDNSService = nullptr;
 static nsHTMLDNSPrefetch::nsDeferrals *sPrefetches = nullptr;
 static nsHTMLDNSPrefetch::nsListener *sDNSListener = nullptr;
+bool sEsniEnabled;
 
 nsresult
 nsHTMLDNSPrefetch::Initialize()
 {
   if (sInitialized) {
     NS_WARNING("Initialize() called twice");
     return NS_OK;
   }
@@ -58,20 +59,25 @@ nsHTMLDNSPrefetch::Initialize()
   sDNSListener = new nsHTMLDNSPrefetch::nsListener();
   NS_ADDREF(sDNSListener);
 
   sPrefetches->Activate();
 
   Preferences::AddBoolVarCache(&sDisablePrefetchHTTPSPref,
                                "network.dns.disablePrefetchFromHTTPS");
 
+  Preferences::AddBoolVarCache(&sEsniEnabled,
+                               "network.security.esni.enabled");
+
   // Default is false, so we need an explicit call to prime the cache.
   sDisablePrefetchHTTPSPref =
     Preferences::GetBool("network.dns.disablePrefetchFromHTTPS", true);
 
+  sEsniEnabled = Preferences::GetBool("network.security.esni.enabled", false);
+
   NS_IF_RELEASE(sDNSService);
   nsresult rv;
   rv = CallGetService(kDNSServiceCID, &sDNSService);
   if (NS_FAILED(rv)) return rv;
 
   if (IsNeckoChild())
     NeckoChild::InitNeckoChild();
 
@@ -124,132 +130,170 @@ nsHTMLDNSPrefetch::PrefetchMedium(Link *
 
 nsresult
 nsHTMLDNSPrefetch::PrefetchHigh(Link *aElement)
 {
   return Prefetch(aElement, 0);
 }
 
 nsresult
-nsHTMLDNSPrefetch::Prefetch(const nsAString &hostname,
+nsHTMLDNSPrefetch::Prefetch(const nsAString &hostname, bool isHttps,
                             const OriginAttributes &aOriginAttributes,
                             uint16_t flags)
 {
   if (IsNeckoChild()) {
     // We need to check IsEmpty() because net_IsValidHostName()
     // considers empty strings to be valid hostnames
     if (!hostname.IsEmpty() &&
         net_IsValidHostName(NS_ConvertUTF16toUTF8(hostname))) {
       // during shutdown gNeckoChild might be null
       if (gNeckoChild) {
-        gNeckoChild->SendHTMLDNSPrefetch(nsString(hostname),
+        gNeckoChild->SendHTMLDNSPrefetch(nsString(hostname), isHttps,
                                          aOriginAttributes, flags);
       }
     }
     return NS_OK;
   }
 
   if (!(sInitialized && sDNSService && sPrefetches && sDNSListener))
     return NS_ERROR_NOT_AVAILABLE;
 
   nsCOMPtr<nsICancelable> tmpOutstanding;
-  return sDNSService->AsyncResolveNative(NS_ConvertUTF16toUTF8(hostname),
-                                         flags | nsIDNSService::RESOLVE_SPECULATE,
-                                         sDNSListener, nullptr, aOriginAttributes,
-                                         getter_AddRefs(tmpOutstanding));
+  nsresult rv = sDNSService->AsyncResolveNative(NS_ConvertUTF16toUTF8(hostname),
+                                                flags | nsIDNSService::RESOLVE_SPECULATE,
+                                                sDNSListener, nullptr, aOriginAttributes,
+                                                getter_AddRefs(tmpOutstanding));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // Fetch ESNI keys if needed.
+  if (isHttps && sEsniEnabled) {
+    nsAutoCString esniHost;
+    esniHost.Append("_esni.");
+    esniHost.Append(NS_ConvertUTF16toUTF8(hostname));
+    Unused << sDNSService->AsyncResolveByTypeNative(esniHost,
+                                                    nsIDNSService::RESOLVE_TYPE_TXT,
+                                                    flags | nsIDNSService::RESOLVE_SPECULATE,
+                                                    sDNSListener, nullptr, aOriginAttributes,
+                                                    getter_AddRefs(tmpOutstanding));
+  }
+
+  return NS_OK;
 }
 
 nsresult
-nsHTMLDNSPrefetch::PrefetchLow(const nsAString &hostname,
+nsHTMLDNSPrefetch::PrefetchLow(const nsAString &hostname, bool isHttps,
                                const OriginAttributes &aOriginAttributes)
 {
-  return Prefetch(hostname, aOriginAttributes, nsIDNSService::RESOLVE_PRIORITY_LOW);
+  return Prefetch(hostname, isHttps, aOriginAttributes,
+                  nsIDNSService::RESOLVE_PRIORITY_LOW);
 }
 
 nsresult
-nsHTMLDNSPrefetch::PrefetchMedium(const nsAString &hostname,
+nsHTMLDNSPrefetch::PrefetchMedium(const nsAString &hostname, bool isHttps,
                                   const OriginAttributes &aOriginAttributes)
 {
-  return Prefetch(hostname, aOriginAttributes, nsIDNSService::RESOLVE_PRIORITY_MEDIUM);
+  return Prefetch(hostname, isHttps, aOriginAttributes,
+                  nsIDNSService::RESOLVE_PRIORITY_MEDIUM);
 }
 
 nsresult
-nsHTMLDNSPrefetch::PrefetchHigh(const nsAString &hostname,
+nsHTMLDNSPrefetch::PrefetchHigh(const nsAString &hostname, bool isHttps,
                                 const OriginAttributes &aOriginAttributes)
 {
-  return Prefetch(hostname, aOriginAttributes, 0);
+  return Prefetch(hostname, isHttps, aOriginAttributes, 0);
 }
 
 nsresult
 nsHTMLDNSPrefetch::CancelPrefetch(Link *aElement,
                                   uint16_t flags,
                                   nsresult aReason)
 {
   if (!(sInitialized && sPrefetches && sDNSService && sDNSListener))
     return NS_ERROR_NOT_AVAILABLE;
 
   nsAutoString hostname;
   aElement->GetHostname(hostname);
 
   Element* element = aElement->GetElement();
   NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
 
-  return CancelPrefetch(hostname,
+  nsAutoString protocol;
+  aElement->GetProtocol(protocol);
+  bool isHttps = false;
+  if (protocol.EqualsLiteral("https:")) {
+    isHttps = true;
+  }
+  return CancelPrefetch(hostname, isHttps,
                         element->NodePrincipal()
                                ->OriginAttributesRef(),
                         flags, aReason);
 }
 
 nsresult
-nsHTMLDNSPrefetch::CancelPrefetch(const nsAString &hostname,
+nsHTMLDNSPrefetch::CancelPrefetch(const nsAString &hostname, bool isHttps,
                                   const OriginAttributes &aOriginAttributes,
                                   uint16_t flags,
                                   nsresult aReason)
 {
   // Forward this request to Necko Parent if we're a child process
   if (IsNeckoChild()) {
     // We need to check IsEmpty() because net_IsValidHostName()
     // considers empty strings to be valid hostnames
     if (!hostname.IsEmpty() &&
         net_IsValidHostName(NS_ConvertUTF16toUTF8(hostname))) {
       // during shutdown gNeckoChild might be null
       if (gNeckoChild) {
         gNeckoChild->SendCancelHTMLDNSPrefetch(nsString(hostname),
+                                               isHttps,
                                                aOriginAttributes,
                                                flags,
                                                aReason);
       }
     }
     return NS_OK;
   }
 
   if (!(sInitialized && sDNSService && sPrefetches && sDNSListener))
     return NS_ERROR_NOT_AVAILABLE;
 
   // Forward cancellation to DNS service
-  return sDNSService->CancelAsyncResolveNative(NS_ConvertUTF16toUTF8(hostname),
-                                               flags
-                                               | nsIDNSService::RESOLVE_SPECULATE,
-                                               sDNSListener, aReason, aOriginAttributes);
+  nsresult rv = sDNSService->CancelAsyncResolveNative(NS_ConvertUTF16toUTF8(hostname),
+                                                      flags
+                                                      | nsIDNSService::RESOLVE_SPECULATE,
+                                                      sDNSListener, aReason, aOriginAttributes);
+  // Cancel fetching ESNI keys if needed.
+  if (sEsniEnabled && isHttps) {
+    nsAutoCString esniHost;
+    esniHost.Append("_esni.");
+    esniHost.Append(NS_ConvertUTF16toUTF8(hostname));
+    sDNSService->CancelAsyncResolveByTypeNative(esniHost,
+                                                nsIDNSService::RESOLVE_TYPE_TXT,
+                                                flags
+                                                | nsIDNSService::RESOLVE_SPECULATE,
+                                                sDNSListener, aReason, aOriginAttributes);
+  }
+  return rv;
 }
 
 nsresult
 nsHTMLDNSPrefetch::CancelPrefetchLow(Link *aElement, nsresult aReason)
 {
   return CancelPrefetch(aElement, nsIDNSService::RESOLVE_PRIORITY_LOW,
                         aReason);
 }
 
 nsresult
-nsHTMLDNSPrefetch::CancelPrefetchLow(const nsAString &hostname,
+nsHTMLDNSPrefetch::CancelPrefetchLow(const nsAString &hostname, bool isHttps,
                                      const OriginAttributes &aOriginAttributes,
                                      nsresult aReason)
 {
-  return CancelPrefetch(hostname, aOriginAttributes, nsIDNSService::RESOLVE_PRIORITY_LOW,
-                        aReason);
+  return CancelPrefetch(hostname, isHttps, aOriginAttributes,
+                        nsIDNSService::RESOLVE_PRIORITY_LOW, aReason);
 }
 
 void
 nsHTMLDNSPrefetch::LinkDestroyed(Link* aLink)
 {
   MOZ_ASSERT(aLink->IsInDNSPrefetch());
   if (sPrefetches) {
     // Clean up all the possible links at once.
@@ -265,16 +309,24 @@ NS_IMPL_ISUPPORTS(nsHTMLDNSPrefetch::nsL
 NS_IMETHODIMP
 nsHTMLDNSPrefetch::nsListener::OnLookupComplete(nsICancelable *request,
                                               nsIDNSRecord  *rec,
                                               nsresult       status)
 {
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsListener::OnLookupByTypeComplete(nsICancelable      *request,
+                                                      nsIDNSByTypeRecord *res,
+                                                      nsresult            status)
+{
+  return NS_OK;
+}
+
 /////////////////////////////////////////////////////////////////////////////////////////////////////////
 
 nsHTMLDNSPrefetch::nsDeferrals::nsDeferrals()
   : mHead(0),
     mTail(0),
     mActiveLoaderCount(0),
     mTimerArmed(false)
 {
@@ -347,43 +399,61 @@ nsHTMLDNSPrefetch::nsDeferrals::SubmitQu
       // Only prefetch here if request was deferred and deferral not cancelled
       if (link && link->HasDeferredDNSPrefetchRequest()) {
         nsCOMPtr<nsIURI> hrefURI(link ? link->GetURI() : nullptr);
         bool isLocalResource = false;
         nsresult rv = NS_OK;
         Element* element = link->GetElement();
 
         hostName.Truncate();
+        bool isHttps = false;
         if (hrefURI) {
           hrefURI->GetAsciiHost(hostName);
           rv = NS_URIChainHasFlags(hrefURI,
                                    nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
                                    &isLocalResource);
+
+          hrefURI->SchemeIs("https", &isHttps);
         }
 
         if (!hostName.IsEmpty() && NS_SUCCEEDED(rv) && !isLocalResource &&
             element) {
           if (IsNeckoChild()) {
             // during shutdown gNeckoChild might be null
             if (gNeckoChild) {
               gNeckoChild->SendHTMLDNSPrefetch(NS_ConvertUTF8toUTF16(hostName),
+                                               isHttps,
                                                element->NodePrincipal()
                                                       ->OriginAttributesRef(),
                                                mEntries[mTail].mFlags);
             }
           } else {
             nsCOMPtr<nsICancelable> tmpOutstanding;
 
             rv = sDNSService->AsyncResolveNative(hostName,
                                                  mEntries[mTail].mFlags
                                                  | nsIDNSService::RESOLVE_SPECULATE,
                                                  sDNSListener, nullptr,
                                                  element->NodePrincipal()
                                                         ->OriginAttributesRef(),
                                                  getter_AddRefs(tmpOutstanding));
+            // Fetch ESNI keys if needed.
+            if (NS_SUCCEEDED(rv) && sEsniEnabled && isHttps) {
+              nsAutoCString esniHost;
+              esniHost.Append("_esni.");
+              esniHost.Append(hostName);
+              sDNSService->AsyncResolveByTypeNative(esniHost,
+                                                    nsIDNSService::RESOLVE_TYPE_TXT,
+                                                    mEntries[mTail].mFlags
+                                                    | nsIDNSService::RESOLVE_SPECULATE,
+                                                    sDNSListener, nullptr,
+                                                    element->NodePrincipal()
+                                                           ->OriginAttributesRef(),
+                                                    getter_AddRefs(tmpOutstanding));
+            }
             // Tell link that deferred prefetch was requested
             if (NS_SUCCEEDED(rv))
               link->OnDNSPrefetchRequested();
           }
         }
       }
     }
 
--- a/dom/html/nsHTMLDNSPrefetch.h
+++ b/dom/html/nsHTMLDNSPrefetch.h
@@ -46,36 +46,36 @@ public:
   // complete, while the string versions submit the lookup to
   // the DNS system immediately. The URI version is somewhat lighter
   // weight, but its request is also more likely to be dropped due to a
   // full queue and it may only be used from the main thread.
 
   static nsresult PrefetchHigh(mozilla::dom::Link *aElement);
   static nsresult PrefetchMedium(mozilla::dom::Link *aElement);
   static nsresult PrefetchLow(mozilla::dom::Link *aElement);
-  static nsresult PrefetchHigh(const nsAString &host,
+  static nsresult PrefetchHigh(const nsAString &host, bool isHttps,
                                const mozilla::OriginAttributes &aOriginAttributes);
-  static nsresult PrefetchMedium(const nsAString &host,
+  static nsresult PrefetchMedium(const nsAString &host, bool isHttps,
                                  const mozilla::OriginAttributes &aOriginAttributes);
-  static nsresult PrefetchLow(const nsAString &host,
+  static nsresult PrefetchLow(const nsAString &host, bool isHttps,
                               const mozilla::OriginAttributes &aOriginAttributes);
-  static nsresult CancelPrefetchLow(const nsAString &host,
+  static nsresult CancelPrefetchLow(const nsAString &host, bool isHttps,
                                     const mozilla::OriginAttributes &aOriginAttributes,
                                     nsresult aReason);
   static nsresult CancelPrefetchLow(mozilla::dom::Link *aElement,
                                     nsresult aReason);
 
   static void LinkDestroyed(mozilla::dom::Link* aLink);
 
 private:
-  static nsresult Prefetch(const nsAString &host,
+  static nsresult Prefetch(const nsAString &host, bool isHttps,
                            const mozilla::OriginAttributes &aOriginAttributes,
                            uint16_t flags);
   static nsresult Prefetch(mozilla::dom::Link *aElement, uint16_t flags);
-  static nsresult CancelPrefetch(const nsAString &hostname,
+  static nsresult CancelPrefetch(const nsAString &hostname, bool isHttps,
                                  const mozilla::OriginAttributes &aOriginAttributes,
                                  uint16_t flags,
                                  nsresult aReason);
   static nsresult CancelPrefetch(mozilla::dom::Link *aElement,
                                  uint16_t flags,
                                  nsresult aReason);
 
 public:
--- a/media/mtransport/nriceresolver.h
+++ b/media/mtransport/nriceresolver.h
@@ -94,16 +94,22 @@ class NrIceResolver
                       int (*cb)(void *cb_arg, nr_transport_addr *addr),
                       void *cb_arg) :
         thread_(thread),
         port_(port),
         transport_(transport),
         cb_(cb), cb_arg_(cb_arg) {}
     NS_IMETHOD OnLookupComplete(nsICancelable *request, nsIDNSRecord *record,
                                 nsresult status) override;
+    NS_IMETHOD OnLookupByTypeComplete(nsICancelable *request,
+                                      nsIDNSByTypeRecord *res,
+                                      nsresult status) override
+    {
+      return NS_OK;
+    }
     int cancel();
     nsCOMPtr<nsICancelable> request_;
     NS_DECL_THREADSAFE_ISUPPORTS
 
    private:
     virtual ~PendingResolution(){};
     nsCOMPtr<nsIEventTarget> thread_;
     uint16_t port_;
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1866,16 +1866,21 @@ pref("network.sts.poll_busy_wait_period_
 pref("network.sts.max_time_for_pr_close_during_shutdown", 5000);
 
 // When the polling socket pair we use to wake poll() up on demand doesn't
 // get signalled (is not readable) within this timeout, we try to repair it.
 // This timeout can be disabled by setting this pref to 0.
 // The value is expected in seconds.
 pref("network.sts.pollable_event_timeout", 6);
 
+// Enable/disable sni encryption.
+// Currently this does not work even if the pref is true, the nss part is
+// missing.
+pref("network.security.esni.enabled", false);
+
 // 2147483647 == PR_INT32_MAX == ~2 GB
 pref("network.websocket.max-message-size", 2147483647);
 
 // Should we automatically follow http 3xx redirects during handshake
 pref("network.websocket.auto-follow-http-redirects", false);
 
 // the number of seconds to wait for websocket connection to be opened
 pref("network.websocket.timeout.open", 20);
--- a/netwerk/base/Dashboard.cpp
+++ b/netwerk/base/Dashboard.cpp
@@ -318,16 +318,24 @@ LookupHelper::OnLookupComplete(nsICancel
     mEventTarget->Dispatch(NewRunnableMethod<RefPtr<LookupArgument>>
                            ("net::LookupHelper::ConstructAnswer",
                             this, &LookupHelper::ConstructAnswer, arg),
                            NS_DISPATCH_NORMAL);
 
     return NS_OK;
 }
 
+NS_IMETHODIMP
+LookupHelper::OnLookupByTypeComplete(nsICancelable *aRequest,
+                                     nsIDNSByTypeRecord *aRes,
+                                     nsresult aStatus)
+{
+    return NS_OK;
+}
+
 nsresult
 LookupHelper::ConstructAnswer(LookupArgument *aArgument)
 {
     nsIDNSRecord *aRecord = aArgument->mRecord;
     AutoSafeJSContext cx;
 
     mozilla::dom::DNSLookupDict dict;
     dict.mAddress.Construct();
--- a/netwerk/base/Predictor.cpp
+++ b/netwerk/base/Predictor.cpp
@@ -93,16 +93,18 @@ static const uint32_t FLAG_PREFETCHABLE 
 
 // We save 12 bits in the "flags" section of our metadata for actual flags, the
 // rest are to keep track of a rolling count of which loads a resource has been
 // used on to determine if we can prefetch that resource or not;
 static const uint8_t kRollingLoadOffset = 12;
 static const int32_t kMaxPrefetchRollingLoadCount = 20;
 static const uint32_t kFlagsMask = ((1 << kRollingLoadOffset) - 1);
 
+static bool sEsniEnabled = false;
+
 // ID Extensions for cache entries
 #define PREDICTOR_ORIGIN_EXTENSION "predictor-origin"
 
 // Get the full origin (scheme, host, port) out of a URI (maybe should be part
 // of nsIURI instead?)
 static nsresult
 ExtractOrigin(nsIURI *uri, nsIURI **originUri, nsIIOService *ioService)
 {
@@ -142,16 +144,24 @@ NS_IMPL_ISUPPORTS(Predictor::DNSListener
 NS_IMETHODIMP
 Predictor::DNSListener::OnLookupComplete(nsICancelable *request,
                                          nsIDNSRecord *rec,
                                          nsresult status)
 {
   return NS_OK;
 }
 
+NS_IMETHODIMP
+Predictor::DNSListener::OnLookupByTypeComplete(nsICancelable *request,
+                                               nsIDNSByTypeRecord *res,
+                                               nsresult status)
+{
+  return NS_OK;
+}
+
 // Class to proxy important information from the initial predictor call through
 // the cache API and back into the internals of the predictor. We can't use the
 // predictor itself, as it may have multiple actions in-flight, and each action
 // has different parameters.
 NS_IMPL_ISUPPORTS(Predictor::Action, nsICacheEntryOpenCallback);
 
 Predictor::Action::Action(bool fullUri, bool predict,
                           Predictor::Reason reason,
@@ -449,16 +459,18 @@ Predictor::Init()
   NS_ENSURE_SUCCESS(rv, rv);
 
   mSpeculativeService = do_QueryInterface(mIOService, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  Preferences::AddBoolVarCache(&sEsniEnabled, "network.security.esni.enabled");
+
   mInitialized = true;
 
   return rv;
 }
 
 namespace {
 class PredictorThreadShutdownRunner : public Runnable
 {
@@ -1417,16 +1429,32 @@ Predictor::RunPredictions(nsIURI *referr
     uri->GetAsciiHost(hostname);
     PREDICTOR_LOG(("    doing preresolve %s", hostname.get()));
     nsCOMPtr<nsICancelable> tmpCancelable;
     mDnsService->AsyncResolveNative(hostname,
                                     (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
                                      nsIDNSService::RESOLVE_SPECULATE),
                                     mDNSListener, nullptr, originAttributes,
                                     getter_AddRefs(tmpCancelable));
+
+    bool isHttps;
+    uri->SchemeIs("https", &isHttps);
+    // Fetch esni keys if needed.
+    if (sEsniEnabled && isHttps) {
+      nsAutoCString esniHost;
+      esniHost.Append("_esni.");
+      esniHost.Append(hostname);
+      mDnsService->AsyncResolveByTypeNative(esniHost,
+                                            nsIDNSService::RESOLVE_TYPE_TXT,
+                                            (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
+                                             nsIDNSService::RESOLVE_SPECULATE),
+                                            mDNSListener, nullptr, originAttributes,
+                                            getter_AddRefs(tmpCancelable));
+    }
+
     predicted = true;
     if (verifier) {
       PREDICTOR_LOG(("    sending preresolve verification"));
       verifier->OnPredictDNS(uri);
     }
   }
 
   return predicted;
--- a/netwerk/base/ProxyAutoConfig.cpp
+++ b/netwerk/base/ProxyAutoConfig.cpp
@@ -300,16 +300,23 @@ public:
     }
 
     mRequest = nullptr;
     mStatus = status;
     mResponse = record;
     return NS_OK;
   }
 
+  NS_IMETHOD OnLookupByTypeComplete(nsICancelable *request,
+                                    nsIDNSByTypeRecord *res,
+                                    nsresult status) override
+  {
+    return NS_OK;
+  }
+
   // nsITimerCallback
   NS_IMETHOD Notify(nsITimer *timer) override
   {
     nsCOMPtr<nsICancelable> request(mRequest);
     if (request)
       request->Cancel(NS_ERROR_NET_TIMEOUT);
     mTimer = nullptr;
     return NS_OK;
--- a/netwerk/base/nsDNSPrefetch.cpp
+++ b/netwerk/base/nsDNSPrefetch.cpp
@@ -7,25 +7,28 @@
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsThreadUtils.h"
 
 #include "nsIDNSListener.h"
 #include "nsIDNSService.h"
 #include "nsICancelable.h"
 #include "nsIURI.h"
+#include "mozilla/Preferences.h"
 
 static nsIDNSService *sDNSService = nullptr;
+static bool sESNIEnabled = false;
 
 nsresult
 nsDNSPrefetch::Initialize(nsIDNSService *aDNSService)
 {
     NS_IF_RELEASE(sDNSService);
     sDNSService =  aDNSService;
     NS_IF_ADDREF(sDNSService);
+    mozilla::Preferences::AddBoolVarCache(&sESNIEnabled, "network.security.esni.enabled");
     return NS_OK;
 }
 
 nsresult
 nsDNSPrefetch::Shutdown()
 {
     NS_IF_RELEASE(sDNSService);
     return NS_OK;
@@ -35,40 +38,64 @@ nsDNSPrefetch::nsDNSPrefetch(nsIURI *aUR
                              mozilla::OriginAttributes& aOriginAttributes,
                              nsIDNSListener *aListener,
                              bool storeTiming)
     : mOriginAttributes(aOriginAttributes)
     , mStoreTiming(storeTiming)
     , mListener(do_GetWeakReference(aListener))
 {
     aURI->GetAsciiHost(mHostname);
+    mIsHttps = false;
+    aURI->SchemeIs("https", &mIsHttps);
 }
 
 nsresult
 nsDNSPrefetch::Prefetch(uint16_t flags)
 {
+    // This can work properly only if this call is on the main thread.
+    // Curenlty we use nsDNSPrefetch only in nsHttpChannel which will call
+    // PrefetchHigh() from the main thread. Let's add assertion to catch
+    // if things change.
+    MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread.");
+
     if (mHostname.IsEmpty())
         return NS_ERROR_NOT_AVAILABLE;
 
     if (!sDNSService)
         return NS_ERROR_NOT_AVAILABLE;
 
     nsCOMPtr<nsICancelable> tmpOutstanding;
 
     if (mStoreTiming)
         mStartTimestamp = mozilla::TimeStamp::Now();
     // If AsyncResolve fails, for example because prefetching is disabled,
     // then our timing will be useless. However, in such a case,
     // mEndTimestamp will be a null timestamp and callers should check
     // TimingsValid() before using the timing.
     nsCOMPtr<nsIEventTarget> main = mozilla::GetMainThreadEventTarget();
-    return sDNSService->AsyncResolveNative(mHostname,
-                                           flags | nsIDNSService::RESOLVE_SPECULATE,
-                                           this, main, mOriginAttributes,
-                                           getter_AddRefs(tmpOutstanding));
+
+    nsresult rv = sDNSService->AsyncResolveNative(mHostname,
+                                                  flags | nsIDNSService::RESOLVE_SPECULATE,
+                                                  this, main, mOriginAttributes,
+                                                  getter_AddRefs(tmpOutstanding));
+    if (NS_FAILED(rv)) {
+        return rv;
+    }
+
+    // Fetch esni keys if needed.
+    if (sESNIEnabled && mIsHttps) {
+        nsAutoCString esniHost;
+        esniHost.Append("_esni.");
+        esniHost.Append(mHostname);
+        sDNSService->AsyncResolveByTypeNative(esniHost, nsIDNSService::RESOLVE_TYPE_TXT,
+                                              flags | nsIDNSService::RESOLVE_SPECULATE,
+                                              this, main, mOriginAttributes,
+                                              getter_AddRefs(tmpOutstanding));
+    }
+    return NS_OK;
 }
 
 nsresult
 nsDNSPrefetch::PrefetchLow(bool refreshDNS)
 {
     return Prefetch(nsIDNSService::RESOLVE_PRIORITY_LOW |
       (refreshDNS ? nsIDNSService::RESOLVE_BYPASS_CACHE : 0));
 }
@@ -82,27 +109,43 @@ nsDNSPrefetch::PrefetchMedium(bool refre
 
 nsresult
 nsDNSPrefetch::PrefetchHigh(bool refreshDNS)
 {
     return Prefetch(refreshDNS ?
                     nsIDNSService::RESOLVE_BYPASS_CACHE : 0);
 }
 
-
 NS_IMPL_ISUPPORTS(nsDNSPrefetch, nsIDNSListener)
 
 NS_IMETHODIMP
 nsDNSPrefetch::OnLookupComplete(nsICancelable *request,
                                 nsIDNSRecord  *rec,
                                 nsresult       status)
 {
     MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread.");
 
     if (mStoreTiming) {
         mEndTimestamp = mozilla::TimeStamp::Now();
     }
     nsCOMPtr<nsIDNSListener> listener = do_QueryReferent(mListener);
     if (listener) {
-      listener->OnLookupComplete(request, rec, status);
+        listener->OnLookupComplete(request, rec, status);
     }
     return NS_OK;
 }
+
+NS_IMETHODIMP
+nsDNSPrefetch::OnLookupByTypeComplete(nsICancelable *request,
+                                      nsIDNSByTypeRecord *res,
+                                      nsresult       status)
+{
+    MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread.");
+
+    if (mStoreTiming) {
+        mEndTimestamp = mozilla::TimeStamp::Now();
+    }
+    nsCOMPtr<nsIDNSListener> listener = do_QueryReferent(mListener);
+    if (listener) {
+        listener->OnLookupByTypeComplete(request, res, status);
+    }
+    return NS_OK;
+}
--- a/netwerk/base/nsDNSPrefetch.h
+++ b/netwerk/base/nsDNSPrefetch.h
@@ -39,16 +39,17 @@ public:
 
     // Call one of the following methods to start the Prefetch.
     nsresult PrefetchHigh(bool refreshDNS = false);
     nsresult PrefetchMedium(bool refreshDNS = false);
     nsresult PrefetchLow(bool refreshDNS = false);
 
 private:
     nsCString mHostname;
+    bool mIsHttps;
     mozilla::OriginAttributes mOriginAttributes;
     bool mStoreTiming;
     mozilla::TimeStamp mStartTimestamp;
     mozilla::TimeStamp mEndTimestamp;
     nsWeakPtr mListener;
 
     nsresult Prefetch(uint16_t flags);
 };
--- a/netwerk/base/nsISocketTransport.idl
+++ b/netwerk/base/nsISocketTransport.idl
@@ -248,16 +248,22 @@ interface nsISocketTransport : nsITransp
 
     /**
      * If this flag is set then it means that if connecting the preferred ip
      * family has failed, retry with the oppsite one once more.
      */
     const unsigned long RETRY_WITH_DIFFERENT_IP_FAMILY = (1 << 10);
 
     /**
+     * If we know that a server speaks only tls <1.3 there is no need to try
+     * to use esni and query dns for esni keys.
+     */
+    const unsigned long DONT_TRY_ESNI = (1 << 11);
+
+    /**
      * An opaque flags for non-standard behavior of the TLS system.
      * It is unlikely this will need to be set outside of telemetry studies
      * relating to the TLS implementation.
      */
     attribute unsigned long tlsFlags;
 
     /**
      * Socket QoS/ToS markings. Valid values are IPTOS_DSCP_AFxx or
@@ -290,9 +296,15 @@ interface nsISocketTransport : nsITransp
     readonly attribute nsresult firstRetryError;
 
     /**
      * If true, this socket transport has found out the prefered family
      * according it's connection flags could not be used to establish
      * connections any more.  Hence, the preference should be reset.
      */
     readonly attribute boolean resetIPFamilyPreference;
+
+    /**
+     * This attribute holds information whether esni has been used.
+     * The value is set after PR_Connect is called.
+     */
+   readonly attribute boolean esniUsed;
 };
--- a/netwerk/base/nsSocketTransport2.cpp
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -28,16 +28,17 @@
 #include "nsISocketProviderService.h"
 #include "nsISocketProvider.h"
 #include "nsISSLSocketControl.h"
 #include "nsIPipe.h"
 #include "nsIClassInfoImpl.h"
 #include "nsURLHelper.h"
 #include "nsIDNSService.h"
 #include "nsIDNSRecord.h"
+#include "nsIDNSByTypeRecord.h"
 #include "nsICancelable.h"
 #include "TCPFastOpenLayer.h"
 #include <algorithm>
 
 #include "nsPrintfCString.h"
 #include "xpcpublic.h"
 
 #if defined(XP_WIN)
@@ -768,16 +769,20 @@ nsSocketTransport::nsSocketTransport()
     , mResetFamilyPreference(false)
     , mTlsFlags(0)
     , mReuseAddrPort(false)
     , mState(STATE_CLOSED)
     , mAttached(false)
     , mInputClosed(true)
     , mOutputClosed(true)
     , mResolving(false)
+    , mDNSLookupStatus(NS_OK)
+    , mDNSARequestFinished(0)
+    , mEsniQueried(false)
+    , mEsniUsed(false)
     , mNetAddrIsSet(false)
     , mSelfAddrIsSet(false)
     , mLock("nsSocketTransport.mLock")
     , mFD(this)
     , mFDref(0)
     , mFDconnected(false)
     , mFDFastOpenInProgress(false)
     , mSocketTransportService(gSocketTransportService)
@@ -1132,18 +1137,55 @@ nsSocketTransport::ResolveHost()
 
     SendStatus(NS_NET_STATUS_RESOLVING_HOST);
 
     if (!SocketHost().Equals(mOriginHost)) {
         SOCKET_LOG(("nsSocketTransport %p origin %s doing dns for %s\n",
                     this, mOriginHost.get(), SocketHost().get()));
     }
     rv = dns->AsyncResolveNative(SocketHost(), dnsFlags,
-                                 this, nullptr, mOriginAttributes,
+                                 this, mSocketTransportService,
+                                 mOriginAttributes,
                                  getter_AddRefs(mDNSRequest));
+    mEsniQueried = false;
+    if (mSocketTransportService->IsEsniEnabled() &&
+        NS_SUCCEEDED(rv) &&
+        !(mConnectionFlags & (DONT_TRY_ESNI | BE_CONSERVATIVE))) {
+
+        bool isSSL = false;
+        for (unsigned int i = 0; i < mTypeCount; ++i) {
+            if (!strcmp(mTypes[i], "ssl")) {
+                isSSL = true;
+                break;
+            }
+        }
+        if (isSSL) {
+            SOCKET_LOG((" look for esni txt record"));
+            nsAutoCString esniHost;
+            esniHost.Append("_esni.");
+            // This might end up being the SocketHost
+            // see https://github.com/ekr/draft-rescorla-tls-esni/issues/61
+            esniHost.Append(mOriginHost);
+            rv = dns->AsyncResolveByTypeNative(esniHost,
+                                               nsIDNSService::RESOLVE_TYPE_TXT,
+                                               dnsFlags,
+                                               this,
+                                               mSocketTransportService,
+                                               mOriginAttributes,
+                                               getter_AddRefs(mDNSTxtRequest));
+            if (NS_FAILED(rv)) {
+                SOCKET_LOG(("  dns request by type failed."));
+                mDNSTxtRequest = nullptr;
+                rv = NS_OK;
+            } else {
+                mEsniQueried = true;
+            }
+        }
+    }
+
     if (NS_SUCCEEDED(rv)) {
         SOCKET_LOG(("  advancing to STATE_RESOLVING\n"));
         mState = STATE_RESOLVING;
     }
     return rv;
 }
 
 nsresult
@@ -1535,16 +1577,29 @@ nsSocketTransport::InitiateSocket()
       u_long nonblocking = 1;
       if (ioctlsocket(osfd, FIONBIO, &nonblocking) != 0) {
         NS_WARNING("Socket could not be set non-blocking!");
         return NS_ERROR_FAILURE;
       }
     }
 #endif
 
+    if (!mDNSRecordTxt.IsEmpty() && mSecInfo) {
+        nsCOMPtr<nsISSLSocketControl> secCtrl =
+            do_QueryInterface(mSecInfo);
+        if (secCtrl) {
+            SOCKET_LOG(("nsSocketTransport::InitiateSocket set esni keys."));
+            rv = secCtrl->SetEsniTxt(mDNSRecordTxt);
+            if (NS_FAILED(rv)) {
+                return rv;
+            }
+            mEsniUsed = true;
+        }
+    }
+
     // We use PRIntervalTime here because we need
     // nsIOService::LastOfflineStateChange time and
     // nsIOService::LastConectivityChange time to be atomic.
     PRIntervalTime connectStarted = 0;
     if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
         connectStarted = PR_IntervalNow();
     }
 
@@ -2121,23 +2176,24 @@ nsSocketTransport::OnSocketEvent(uint32_
                 mCondition = ResolveHost();
 
         } else {
             SOCKET_LOG(("  ignoring redundant event\n"));
         }
         break;
 
     case MSG_DNS_LOOKUP_COMPLETE:
-        if (mDNSRequest)  // only send this if we actually resolved anything
+        if (mDNSRequest || mDNSTxtRequest) { // only send this if we actually resolved anything
             SendStatus(NS_NET_STATUS_RESOLVED_HOST);
+        }
 
         SOCKET_LOG(("  MSG_DNS_LOOKUP_COMPLETE\n"));
         mDNSRequest = nullptr;
-        if (param) {
-            mDNSRecord = static_cast<nsIDNSRecord *>(param);
+        mDNSTxtRequest = nullptr;
+        if (mDNSRecord) {
             mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr);
         }
         // status contains DNS lookup status
         if (NS_FAILED(status)) {
             // When using a HTTP proxy, NS_ERROR_UNKNOWN_HOST means the HTTP
             // proxy host is not found, so we fixup the error code.
             // For SOCKS proxies (mProxyTransparent == true), the socket
             // transport resolves the real host here, so there's no fixup
@@ -2398,16 +2454,21 @@ nsSocketTransport::OnSocketDetached(PRFi
         mFastOpenCallback = nullptr;
 
         // make sure there isn't any pending DNS request
         if (mDNSRequest) {
             mDNSRequest->Cancel(NS_ERROR_ABORT);
             mDNSRequest = nullptr;
         }
 
+        if (mDNSTxtRequest) {
+            mDNSTxtRequest->Cancel(NS_ERROR_ABORT);
+            mDNSTxtRequest = nullptr;
+        }
+
         //
         // notify input/output streams
         //
         mInput.OnSocketReady(mCondition);
         mOutput.OnSocketReady(mCondition);
     }
 
     // If FastOpen has been used (mFDFastOpenInProgress==true),
@@ -2955,27 +3016,83 @@ nsSocketTransport::SetSendBufferSize(uin
     return rv;
 }
 
 NS_IMETHODIMP
 nsSocketTransport::OnLookupComplete(nsICancelable *request,
                                     nsIDNSRecord  *rec,
                                     nsresult       status)
 {
+    SOCKET_LOG(("nsSocketTransport::OnLookupComplete: this=%p status %" PRIx32
+                ".", this, static_cast<uint32_t>(status)));
+    if (NS_FAILED(status) && mDNSTxtRequest) {
+      mDNSTxtRequest->Cancel(NS_ERROR_ABORT);
+    } else if (NS_SUCCEEDED(status)) {
+      mDNSRecord = static_cast<nsIDNSRecord *>(rec);
+    }
+
     // flag host lookup complete for the benefit of the ResolveHost method.
-    mResolving = false;
-
-    nsresult rv = PostEvent(MSG_DNS_LOOKUP_COMPLETE, status, rec);
-
-    // if posting a message fails, then we should assume that the socket
-    // transport has been shutdown.  this should never happen!  if it does
-    // it means that the socket transport service was shutdown before the
-    // DNS service.
-    if (NS_FAILED(rv))
-        NS_WARNING("unable to post DNS lookup complete message");
+    if (!mDNSTxtRequest) {
+        if (mEsniQueried) {
+          Telemetry::Accumulate(Telemetry::ESNI_KEYS_RECORD_FETCH_DELAYS, 0);
+        }
+        mResolving = false;
+        nsresult rv = PostEvent(MSG_DNS_LOOKUP_COMPLETE, status, nullptr);
+
+       // if posting a message fails, then we should assume that the socket
+       // transport has been shutdown.  this should never happen!  if it does
+       // it means that the socket transport service was shutdown before the
+       // DNS service.
+       if (NS_FAILED(rv)) {
+           NS_WARNING("unable to post DNS lookup complete message");
+       }
+    } else {
+        mDNSLookupStatus = status; // remember the status to send it when esni lookup is ready.
+        mDNSRequest = nullptr;
+        mDNSARequestFinished = PR_IntervalNow();
+    }
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::OnLookupByTypeComplete(nsICancelable      *request,
+                                          nsIDNSByTypeRecord *txtResponse,
+                                          nsresult            status)
+{
+    SOCKET_LOG(("nsSocketTransport::OnLookupByTypeComplete: "
+                "this=%p status %" PRIx32 ".",
+                this, static_cast<uint32_t>(status)));
+    MOZ_ASSERT(mDNSTxtRequest == request);
+
+    if (NS_SUCCEEDED(status)) {
+        txtResponse->GetRecordsAsOneString(mDNSRecordTxt);
+        mDNSRecordTxt.Trim(" ");
+    }
+    Telemetry::Accumulate(Telemetry::ESNI_KEYS_RECORDS_FOUND,
+                          NS_SUCCEEDED(status));
+    // flag host lookup complete for the benefit of the ResolveHost method.
+    if (!mDNSRequest) {
+        mResolving = false;
+        MOZ_ASSERT(mDNSARequestFinished);
+        Telemetry::Accumulate(Telemetry::ESNI_KEYS_RECORD_FETCH_DELAYS,
+                              PR_IntervalToMilliseconds(PR_IntervalNow() - mDNSARequestFinished));
+
+        nsresult rv = PostEvent(MSG_DNS_LOOKUP_COMPLETE, mDNSLookupStatus, nullptr);
+
+        // if posting a message fails, then we should assume that the socket
+        // transport has been shutdown.  this should never happen!  if it does
+        // it means that the socket transport service was shutdown before the
+        // DNS service.
+        if (NS_FAILED(rv)) {
+            NS_WARNING("unable to post DNS lookup complete message");
+        }
+    } else {
+        mDNSTxtRequest = nullptr;
+    }
 
     return NS_OK;
 }
 
 // nsIInterfaceRequestor
 NS_IMETHODIMP
 nsSocketTransport::GetInterface(const nsIID &iid, void **result)
 {
@@ -3593,10 +3710,17 @@ nsSocketTransport::GetFirstRetryError(ns
 
 NS_IMETHODIMP
 nsSocketTransport::GetResetIPFamilyPreference(bool *aReset)
 {
   *aReset = mResetFamilyPreference;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsSocketTransport::GetEsniUsed(bool *aEsniUsed)
+{
+  *aEsniUsed = mEsniUsed;
+  return NS_OK;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/base/nsSocketTransport2.h
+++ b/netwerk/base/nsSocketTransport2.h
@@ -337,16 +337,23 @@ private:
 
     // this flag is used to determine if the results of a host lookup arrive
     // recursively or not.  this flag is not protected by any lock.
     bool mResolving;
 
     nsCOMPtr<nsICancelable> mDNSRequest;
     nsCOMPtr<nsIDNSRecord>  mDNSRecord;
 
+    nsresult                mDNSLookupStatus;
+    PRIntervalTime          mDNSARequestFinished;
+    nsCOMPtr<nsICancelable> mDNSTxtRequest;
+    nsCString               mDNSRecordTxt;
+    bool                    mEsniQueried;
+    bool                    mEsniUsed;
+
     // mNetAddr/mSelfAddr is valid from GetPeerAddr()/GetSelfAddr() once we have
     // reached STATE_TRANSFERRING. It must not change after that.
     void                    SetSocketName(PRFileDesc *fd);
     NetAddr                 mNetAddr;
     NetAddr                 mSelfAddr; // getsockname()
     Atomic<bool, Relaxed>   mNetAddrIsSet;
     Atomic<bool, Relaxed>   mSelfAddrIsSet;
 
--- a/netwerk/base/nsSocketTransportService2.cpp
+++ b/netwerk/base/nsSocketTransportService2.cpp
@@ -50,16 +50,18 @@ static Atomic<PRThread*, Relaxed> gSocke
 #define SOCKET_LIMIT_TARGET 1000U
 #define SOCKET_LIMIT_MIN      50U
 #define MAX_TIME_BETWEEN_TWO_POLLS "network.sts.max_time_for_events_between_two_polls"
 #define POLL_BUSY_WAIT_PERIOD "network.sts.poll_busy_wait_period"
 #define POLL_BUSY_WAIT_PERIOD_TIMEOUT "network.sts.poll_busy_wait_period_timeout"
 #define TELEMETRY_PREF "toolkit.telemetry.enabled"
 #define MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN "network.sts.max_time_for_pr_close_during_shutdown"
 #define POLLABLE_EVENT_TIMEOUT "network.sts.pollable_event_timeout"
+#define ESNI_ENABLED "network.security.esni.enabled"
+#define ESNI_DISABLED_MITM "security.pki.mitm_detected" 
 
 #define REPAIR_POLLABLE_EVENT_TIME 10
 
 uint32_t nsSocketTransportService::gMaxCount;
 PRCallOnceType nsSocketTransportService::gMaxCountInitOnce;
 
 // Utility functions
 bool
@@ -153,16 +155,19 @@ nsSocketTransportService::nsSocketTransp
     , mLastNetworkLinkChangeTime(0)
     , mNetworkLinkChangeBusyWaitPeriod(PR_SecondsToInterval(50))
     , mNetworkLinkChangeBusyWaitTimeout(PR_SecondsToInterval(7))
     , mSleepPhase(false)
     , mProbedMaxCount(false)
 #if defined(XP_WIN)
     , mPolling(false)
 #endif
+    , mEsniEnabled(false)
+    , mTrustedMitmDetected(false)
+    , mNotTrustedMitmDetected(false)
 {
     NS_ASSERTION(NS_IsMainThread(), "wrong thread");
 
     PR_CallOnce(&gMaxCountInitOnce, DiscoverMaxCount);
     mActiveList = (SocketContext *)
         moz_xmalloc(sizeof(SocketContext) * mActiveListSize);
     mIdleList = (SocketContext *)
         moz_xmalloc(sizeof(SocketContext) * mIdleListSize);
@@ -600,16 +605,18 @@ static const char* gCallbackPrefs[] = {
     KEEPALIVE_ENABLED_PREF,
     KEEPALIVE_IDLE_TIME_PREF,
     KEEPALIVE_RETRY_INTERVAL_PREF,
     KEEPALIVE_PROBE_COUNT_PREF,
     MAX_TIME_BETWEEN_TWO_POLLS,
     TELEMETRY_PREF,
     MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN,
     POLLABLE_EVENT_TIMEOUT,
+    ESNI_ENABLED,
+    ESNI_DISABLED_MITM,
     nullptr,
 };
 
 /* static */ void
 nsSocketTransportService::PrefCallback(const char* aPref, nsSocketTransportService* aSelf)
 {
     aSelf->UpdatePrefs();
 }
@@ -1414,16 +1421,28 @@ nsSocketTransportService::UpdatePrefs()
 
     int32_t pollableEventTimeout;
     rv = Preferences::GetInt(POLLABLE_EVENT_TIMEOUT, &pollableEventTimeout);
     if (NS_SUCCEEDED(rv) && pollableEventTimeout >= 0) {
         MutexAutoLock lock(mLock);
         mPollableEventTimeout = TimeDuration::FromSeconds(pollableEventTimeout);
     }
 
+    bool esniPref = false;
+    rv = Preferences::GetBool(ESNI_ENABLED, &esniPref);
+    if (NS_SUCCEEDED(rv)) {
+        mEsniEnabled = esniPref;
+    }
+
+    bool esniMitmPref = false;
+    rv = Preferences::GetBool(ESNI_DISABLED_MITM, &esniMitmPref);
+    if (NS_SUCCEEDED(rv)) {
+        mTrustedMitmDetected = esniMitmPref;
+    }
+
     return NS_OK;
 }
 
 void
 nsSocketTransportService::OnKeepaliveEnabledPrefChange()
 {
     // Dispatch to socket thread if we're not executing there.
     if (!OnSocketThread()) {
@@ -1510,16 +1529,17 @@ nsSocketTransportService::Observe(nsISup
         if (mSleepPhase && !mAfterWakeUpTimer) {
             NS_NewTimerWithObserver(getter_AddRefs(mAfterWakeUpTimer),
                                     this, 2000, nsITimer::TYPE_ONE_SHOT);
         }
     } else if (!strcmp(topic, "xpcom-shutdown-threads")) {
         ShutdownThread();
     } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
         mLastNetworkLinkChangeTime = PR_IntervalNow();
+        mNotTrustedMitmDetected = false;
     }
 
     return NS_OK;
 }
 
 void
 nsSocketTransportService::ClosePrivateConnections()
 {
--- a/netwerk/base/nsSocketTransportService2.h
+++ b/netwerk/base/nsSocketTransportService2.h
@@ -115,16 +115,23 @@ public:
 
     // Returns true if keepalives are enabled in prefs.
     bool IsKeepaliveEnabled() { return mKeepaliveEnabledPref; }
 
     bool IsTelemetryEnabledAndNotSleepPhase() { return mTelemetryEnabledPref &&
                                                        !mSleepPhase; }
     PRIntervalTime MaxTimeForPrClosePref() {return mMaxTimeForPrClosePref; }
 
+    bool IsEsniEnabled() { return mEsniEnabled && !mTrustedMitmDetected &&
+                                  !mNotTrustedMitmDetected; }
+
+    void SetNotTrustedMitmDetected() {
+      mNotTrustedMitmDetected = true;
+    }
+
 protected:
 
     virtual ~nsSocketTransportService();
 
 private:
 
     //-------------------------------------------------------------------------
     // misc (any thread)
@@ -298,16 +305,20 @@ private:
     nsCOMPtr<nsITimer> mPollRepairTimer;
     void StartPollWatchdog();
     void DoPollRepair();
     void StartPolling();
     void EndPolling();
 #endif
 
     void TryRepairPollableEvent();
+
+    bool mEsniEnabled;
+    bool mTrustedMitmDetected;
+    bool mNotTrustedMitmDetected;
 };
 
 extern nsSocketTransportService *gSocketTransportService;
 bool OnSocketThread();
 
 } // namespace net
 } // namespace mozilla
 
--- a/netwerk/base/nsUDPSocket.cpp
+++ b/netwerk/base/nsUDPSocket.cpp
@@ -1098,16 +1098,24 @@ PendingSend::OnLookupComplete(nsICancela
     nsresult rv = mSocket->SendWithAddress(&addr, mData.Elements(),
                                            mData.Length(), &count);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+PendingSend::OnLookupByTypeComplete(nsICancelable      *aRequest,
+                                    nsIDNSByTypeRecord *aRes,
+                                    nsresult            aStatus)
+{
+  return NS_OK;
+}
+
 class PendingSendStream : public nsIDNSListener
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIDNSLISTENER
 
   PendingSendStream(nsUDPSocket *aSocket, uint16_t aPort,
                     nsIInputStream *aStream)
@@ -1139,16 +1147,24 @@ PendingSendStream::OnLookupComplete(nsIC
   if (NS_SUCCEEDED(rec->GetNextAddr(mPort, &addr))) {
     nsresult rv = mSocket->SendBinaryStreamWithAddress(&addr, mStream);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+PendingSendStream::OnLookupByTypeComplete(nsICancelable *aRequest,
+                                          nsIDNSByTypeRecord *aRes,
+                                          nsresult aStatus)
+{
+  return NS_OK;
+}
+
 class SendRequestRunnable: public Runnable {
 public:
   SendRequestRunnable(nsUDPSocket* aSocket,
                       const NetAddr& aAddr,
                       FallibleTArray<uint8_t>&& aData)
     : Runnable("net::SendRequestRunnable")
     , mSocket(aSocket)
     , mAddr(aAddr)
--- a/netwerk/dns/ChildDNSService.cpp
+++ b/netwerk/dns/ChildDNSService.cpp
@@ -50,66 +50,41 @@ ChildDNSService::ChildDNSService()
   , mDisablePrefetch(false)
   , mPendingRequestsLock("DNSPendingRequestsLock")
 {
   MOZ_ASSERT(IsNeckoChild());
 }
 
 void
 ChildDNSService::GetDNSRecordHashKey(const nsACString &aHost,
+                                     uint16_t aType,
                                      const OriginAttributes &aOriginAttributes,
                                      uint32_t aFlags,
                                      nsIDNSListener* aListener,
                                      nsACString &aHashKey)
 {
   aHashKey.Assign(aHost);
+  aHashKey.AppendInt(aType);
 
   nsAutoCString originSuffix;
   aOriginAttributes.CreateSuffix(originSuffix);
   aHashKey.Assign(originSuffix);
 
   aHashKey.AppendInt(aFlags);
   aHashKey.AppendPrintf("%p", aListener);
 }
 
-//-----------------------------------------------------------------------------
-// ChildDNSService::nsIDNSService
-//-----------------------------------------------------------------------------
-
-NS_IMETHODIMP
-ChildDNSService::AsyncResolve(const nsACString  &hostname,
-                              uint32_t           flags,
-                              nsIDNSListener    *listener,
-                              nsIEventTarget    *target_,
-                              JS::HandleValue    aOriginAttributes,
-                              JSContext         *aCx,
-                              uint8_t            aArgc,
-                              nsICancelable    **result)
-{
-  OriginAttributes attrs;
-
-  if (aArgc == 1) {
-    if (!aOriginAttributes.isObject() ||
-        !attrs.Init(aCx, aOriginAttributes)) {
-      return NS_ERROR_INVALID_ARG;
-    }
-  }
-
-  return AsyncResolveNative(hostname, flags,
-                            listener, target_, attrs,
-                            result);
-}
-
-NS_IMETHODIMP
-ChildDNSService::AsyncResolveNative(const nsACString        &hostname,
-                                    uint32_t                 flags,
-                                    nsIDNSListener          *listener,
-                                    nsIEventTarget          *target_,
-                                    const OriginAttributes  &aOriginAttributes,
-                                    nsICancelable          **result)
+nsresult
+ChildDNSService::AsyncResolveInternal(const nsACString        &hostname,
+                                      uint16_t                 type,
+                                      uint32_t                 flags,
+                                      nsIDNSListener          *listener,
+                                      nsIEventTarget          *target_,
+                                      const OriginAttributes  &aOriginAttributes,
+                                      nsICancelable          **result)
 {
   NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE);
 
   if (mDisablePrefetch && (flags & RESOLVE_SPECULATE)) {
     return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
   }
 
   // We need original flags for the pending requests hash.
@@ -133,24 +108,25 @@ ChildDNSService::AsyncResolveNative(cons
   if (target) {
     // Guarantee listener freed on main thread.  Not sure we need this in child
     // (or in parent in nsDNSService.cpp) but doesn't hurt.
     listener = new DNSListenerProxy(listener, target);
   }
 
   RefPtr<DNSRequestChild> childReq =
     new DNSRequestChild(hostname,
+                        type,
                         aOriginAttributes,
                         flags,
                         listener, target);
 
   {
     MutexAutoLock lock(mPendingRequestsLock);
     nsCString key;
-    GetDNSRecordHashKey(hostname, aOriginAttributes, originalFlags,
+    GetDNSRecordHashKey(hostname, type, aOriginAttributes, originalFlags,
                         originalListener, key);
     nsTArray<RefPtr<DNSRequestChild>> *hashEntry;
     if (mPendingRequests.Get(key, &hashEntry)) {
       hashEntry->AppendElement(childReq);
     } else {
       hashEntry = new nsTArray<RefPtr<DNSRequestChild>>();
       hashEntry->AppendElement(childReq);
       mPendingRequests.Put(key, hashEntry);
@@ -158,16 +134,119 @@ ChildDNSService::AsyncResolveNative(cons
   }
 
   childReq->StartRequest();
 
   childReq.forget(result);
   return NS_OK;
 }
 
+nsresult
+ChildDNSService::CancelAsyncResolveInternal(const nsACString       &aHostname,
+                                            uint16_t                aType,
+                                            uint32_t                aFlags,
+                                            nsIDNSListener         *aListener,
+                                            nsresult                aReason,
+                                            const OriginAttributes &aOriginAttributes)
+{
+  if (mDisablePrefetch && (aFlags & RESOLVE_SPECULATE)) {
+    return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
+  }
+
+  MutexAutoLock lock(mPendingRequestsLock);
+  nsTArray<RefPtr<DNSRequestChild>> *hashEntry;
+  nsCString key;
+  GetDNSRecordHashKey(aHostname, aType, aOriginAttributes, aFlags,
+                      aListener, key);
+  if (mPendingRequests.Get(key, &hashEntry)) {
+    // We cancel just one.
+    hashEntry->ElementAt(0)->Cancel(aReason);
+  }
+
+  return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ChildDNSService::nsIDNSService
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ChildDNSService::AsyncResolve(const nsACString  &hostname,
+                              uint32_t           flags,
+                              nsIDNSListener    *listener,
+                              nsIEventTarget    *target_,
+                              JS::HandleValue    aOriginAttributes,
+                              JSContext         *aCx,
+                              uint8_t            aArgc,
+                              nsICancelable    **result)
+{
+  OriginAttributes attrs;
+
+  if (aArgc == 1) {
+    if (!aOriginAttributes.isObject() ||
+        !attrs.Init(aCx, aOriginAttributes)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+  }
+
+  return AsyncResolveInternal(hostname, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+                              flags, listener, target_, attrs, result);
+}
+
+NS_IMETHODIMP
+ChildDNSService::AsyncResolveNative(const nsACString        &hostname,
+                                    uint32_t                 flags,
+                                    nsIDNSListener          *listener,
+                                    nsIEventTarget          *target_,
+                                    const OriginAttributes  &aOriginAttributes,
+                                    nsICancelable          **result)
+{
+  return AsyncResolveInternal(hostname, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+                              flags, listener, target_, aOriginAttributes,
+                              result);
+}
+
+NS_IMETHODIMP
+ChildDNSService::AsyncResolveByType(const nsACString  &hostname,
+                                    uint16_t           type,
+                                    uint32_t           flags,
+                                    nsIDNSListener    *listener,
+                                    nsIEventTarget    *target_,
+                                    JS::HandleValue    aOriginAttributes,
+                                    JSContext         *aCx,
+                                    uint8_t            aArgc,
+                                    nsICancelable    **result)
+{
+  OriginAttributes attrs;
+
+  if (aArgc == 1) {
+    if (!aOriginAttributes.isObject() ||
+        !attrs.Init(aCx, aOriginAttributes)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+  }
+
+  return AsyncResolveInternal(hostname, type, flags,
+                              listener, target_, attrs,
+                              result);
+}
+
+NS_IMETHODIMP
+ChildDNSService::AsyncResolveByTypeNative(const nsACString        &hostname,
+                                          uint16_t                 type,
+                                          uint32_t                 flags,
+                                          nsIDNSListener          *listener,
+                                          nsIEventTarget          *target_,
+                                          const OriginAttributes  &aOriginAttributes,
+                                          nsICancelable          **result)
+{
+  return AsyncResolveInternal(hostname, type, flags, listener, target_,
+                              aOriginAttributes, result);
+}
+
 NS_IMETHODIMP
 ChildDNSService::CancelAsyncResolve(const nsACString  &aHostname,
                                     uint32_t           aFlags,
                                     nsIDNSListener    *aListener,
                                     nsresult           aReason,
                                     JS::HandleValue    aOriginAttributes,
                                     JSContext         *aCx,
                                     uint8_t            aArgc)
@@ -176,42 +255,66 @@ ChildDNSService::CancelAsyncResolve(cons
 
   if (aArgc == 1) {
     if (!aOriginAttributes.isObject() ||
         !attrs.Init(aCx, aOriginAttributes)) {
         return NS_ERROR_INVALID_ARG;
     }
   }
 
-  return CancelAsyncResolveNative(aHostname, aFlags,
-                                  aListener, aReason, attrs);
+  return CancelAsyncResolveInternal(aHostname, nsIDNSService::RESOLVE_TYPE_DEFAULT,
+                                    aFlags, aListener, aReason, attrs);
 }
 
 NS_IMETHODIMP
 ChildDNSService::CancelAsyncResolveNative(const nsACString       &aHostname,
                                           uint32_t                aFlags,
                                           nsIDNSListener         *aListener,
                                           nsresult                aReason,
                                           const OriginAttributes &aOriginAttributes)
 {
-  if (mDisablePrefetch && (aFlags & RESOLVE_SPECULATE)) {
-    return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
+  return CancelAsyncResolveInternal(aHostname,
+                                    nsIDNSService::RESOLVE_TYPE_DEFAULT,
+                                    aFlags, aListener, aReason,
+                                    aOriginAttributes);
+}
+
+NS_IMETHODIMP
+ChildDNSService::CancelAsyncResolveByType(const nsACString  &aHostname,
+                                          uint16_t           aType,
+                                          uint32_t           aFlags,
+                                          nsIDNSListener    *aListener,
+                                          nsresult           aReason,
+                                          JS::HandleValue    aOriginAttributes,
+                                          JSContext         *aCx,
+                                          uint8_t            aArgc)
+{
+  OriginAttributes attrs;
+
+  if (aArgc == 1) {
+    if (!aOriginAttributes.isObject() ||
+        !attrs.Init(aCx, aOriginAttributes)) {
+        return NS_ERROR_INVALID_ARG;
+    }
   }
 
-  MutexAutoLock lock(mPendingRequestsLock);
-  nsTArray<RefPtr<DNSRequestChild>> *hashEntry;
-  nsCString key;
-  GetDNSRecordHashKey(aHostname, aOriginAttributes, aFlags,
-                      aListener, key);
-  if (mPendingRequests.Get(key, &hashEntry)) {
-    // We cancel just one.
-    hashEntry->ElementAt(0)->Cancel(aReason);
-  }
+  return CancelAsyncResolveInternal(aHostname, aType, aFlags,
+                                    aListener, aReason, attrs);
+}
 
-  return NS_OK;
+NS_IMETHODIMP
+ChildDNSService::CancelAsyncResolveByTypeNative(const nsACString       &aHostname,
+                                                uint16_t                aType,
+                                                uint32_t                aFlags,
+                                                nsIDNSListener         *aListener,
+                                                nsresult                aReason,
+                                                const OriginAttributes &aOriginAttributes)
+{
+  return CancelAsyncResolveInternal(aHostname, aType, aFlags, aListener,
+                                    aReason, aOriginAttributes);
 }
 
 NS_IMETHODIMP
 ChildDNSService::Resolve(const nsACString &hostname,
                          uint32_t          flags,
                          JS::HandleValue   aOriginAttributes,
                          JSContext        *aCx,
                          uint8_t           aArgc,
@@ -260,17 +363,18 @@ ChildDNSService::NotifyRequestDone(DNSRe
       MOZ_ASSERT(originalListener);
       return;
     }
   }
 
   MutexAutoLock lock(mPendingRequestsLock);
 
   nsCString key;
-  GetDNSRecordHashKey(aDnsRequest->mHost, aDnsRequest->mOriginAttributes, originalFlags,
+  GetDNSRecordHashKey(aDnsRequest->mHost, aDnsRequest->mType,
+                      aDnsRequest->mOriginAttributes, originalFlags,
                       originalListener, key);
 
   nsTArray<RefPtr<DNSRequestChild>> *hashEntry;
 
   if (mPendingRequests.Get(key, &hashEntry)) {
     int idx;
     if ((idx = hashEntry->IndexOf(aDnsRequest))) {
       hashEntry->RemoveElementAt(idx);
--- a/netwerk/dns/ChildDNSService.h
+++ b/netwerk/dns/ChildDNSService.h
@@ -36,20 +36,34 @@ public:
 
   void NotifyRequestDone(DNSRequestChild *aDnsRequest);
 
   bool GetOffline() const;
 private:
   virtual ~ChildDNSService() = default;
 
   void MOZ_ALWAYS_INLINE GetDNSRecordHashKey(const nsACString &aHost,
+                                             uint16_t aType,
                                              const OriginAttributes &aOriginAttributes,
                                              uint32_t aFlags,
                                              nsIDNSListener* aListener,
                                              nsACString &aHashKey);
+  nsresult AsyncResolveInternal(const nsACString        &hostname,
+                                uint16_t                 type,
+                                uint32_t                 flags,
+                                nsIDNSListener          *listener,
+                                nsIEventTarget          *target_,
+                                const OriginAttributes  &aOriginAttributes,
+                                nsICancelable          **result);
+  nsresult CancelAsyncResolveInternal(const nsACString       &aHostname,
+                                      uint16_t                aType,
+                                      uint32_t                aFlags,
+                                      nsIDNSListener         *aListener,
+                                      nsresult                aReason,
+                                      const OriginAttributes &aOriginAttributes);
 
   bool mFirstTime;
   bool mDisablePrefetch;
 
   // We need to remember pending dns requests to be able to cancel them.
   nsClassHashtable<nsCStringHashKey, nsTArray<RefPtr<DNSRequestChild>>> mPendingRequests;
   Mutex mPendingRequestsLock;
 };
--- a/netwerk/dns/DNSListenerProxy.cpp
+++ b/netwerk/dns/DNSListenerProxy.cpp
@@ -21,23 +21,40 @@ DNSListenerProxy::OnLookupComplete(nsICa
                                    nsresult aStatus)
 {
   RefPtr<OnLookupCompleteRunnable> r =
     new OnLookupCompleteRunnable(mListener, aRequest, aRecord, aStatus);
   return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
 }
 
 NS_IMETHODIMP
+DNSListenerProxy::OnLookupByTypeComplete(nsICancelable* aRequest,
+                                         nsIDNSByTypeRecord *aRes,
+                                         nsresult aStatus)
+{
+  RefPtr<OnLookupByTypeCompleteRunnable> r =
+    new OnLookupByTypeCompleteRunnable(mListener, aRequest, aRes, aStatus);
+  return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
 DNSListenerProxy::OnLookupCompleteRunnable::Run()
 {
   mListener->OnLookupComplete(mRequest, mRecord, mStatus);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+DNSListenerProxy::OnLookupByTypeCompleteRunnable::Run()
+{
+  mListener->OnLookupByTypeComplete(mRequest, mResult, mStatus);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 DNSListenerProxy::GetOriginalListener(nsIDNSListener **aOriginalListener)
 {
   NS_IF_ADDREF(*aOriginalListener = mListener);
   return NS_OK;
 }
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/dns/DNSListenerProxy.h
+++ b/netwerk/dns/DNSListenerProxy.h
@@ -55,16 +55,39 @@ public:
 
   private:
     nsMainThreadPtrHandle<nsIDNSListener> mListener;
     nsCOMPtr<nsICancelable> mRequest;
     nsCOMPtr<nsIDNSRecord> mRecord;
     nsresult mStatus;
   };
 
+  class OnLookupByTypeCompleteRunnable : public Runnable
+  {
+  public:
+    OnLookupByTypeCompleteRunnable(const nsMainThreadPtrHandle<nsIDNSListener> &aListener,
+                                   nsICancelable *aRequest,
+                                   nsIDNSByTypeRecord *aRes,
+                                   nsresult aStatus)
+      : Runnable("DNSListenerProxy::OnLookupByTypeCompleteRunnable")
+      , mListener(aListener)
+      , mRequest(aRequest)
+      , mResult(aRes)
+      , mStatus(aStatus)
+    { }
+
+    NS_DECL_NSIRUNNABLE
+
+  private:
+    nsMainThreadPtrHandle<nsIDNSListener> mListener;
+    nsCOMPtr<nsICancelable> mRequest;
+    nsCOMPtr<nsIDNSByTypeRecord> mResult;
+    nsresult mStatus;
+  };
+
 private:
   ~DNSListenerProxy() {}
 
   nsMainThreadPtrHandle<nsIDNSListener> mListener;
   nsCOMPtr<nsIEventTarget> mTargetThread;
 };
 
 
--- a/netwerk/dns/DNSRequestChild.cpp
+++ b/netwerk/dns/DNSRequestChild.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/net/ChildDNSService.h"
 #include "mozilla/net/DNSRequestChild.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/SystemGroup.h"
 #include "mozilla/Unused.h"
 #include "nsIDNSRecord.h"
+#include "nsIDNSByTypeRecord.h"
 #include "nsHostResolver.h"
 #include "nsTArray.h"
 #include "nsNetAddr.h"
 #include "nsIThread.h"
 #include "nsThreadUtils.h"
 
 using namespace mozilla::ipc;
 
@@ -155,16 +156,55 @@ ChildDNSRecord::Rewind()
 NS_IMETHODIMP
 ChildDNSRecord::ReportUnusable(uint16_t aPort)
 {
   // "We thank you for your feedback" == >/dev/null
   // TODO: we could send info back to parent.
   return NS_OK;
 }
 
+class ChildDNSByTypeRecord : public nsIDNSByTypeRecord
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIDNSBYTYPERECORD
+
+  explicit ChildDNSByTypeRecord(const nsTArray<nsCString> &reply);
+
+private:
+  virtual ~ChildDNSByTypeRecord() = default;
+
+  nsTArray<nsCString> mRecords;
+};
+
+NS_IMPL_ISUPPORTS(ChildDNSByTypeRecord, nsIDNSByTypeRecord)
+
+ChildDNSByTypeRecord::ChildDNSByTypeRecord(const nsTArray<nsCString> &reply)
+{
+  mRecords = reply;
+}
+
+NS_IMETHODIMP
+ChildDNSByTypeRecord::GetRecords(nsTArray<nsCString> &aRecords)
+{
+  aRecords = mRecords;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSByTypeRecord::GetRecordsAsOneString(nsACString &aRecords)
+{
+  // deep copy
+  for (uint32_t i = 0; i < mRecords.Length(); i++) {
+      aRecords.Append(mRecords[i]);
+  }
+  return NS_OK;
+}
+
+
 //-----------------------------------------------------------------------------
 // CancelDNSRequestEvent
 //-----------------------------------------------------------------------------
 
 class CancelDNSRequestEvent : public Runnable
 {
 public:
   CancelDNSRequestEvent(DNSRequestChild* aDnsReq, nsresult aReason)
@@ -173,40 +213,43 @@ public:
     , mReasonForCancel(aReason)
   {}
 
   NS_IMETHOD Run() override
   {
     if (mDnsRequest->mIPCOpen) {
       // Send request to Parent process.
       mDnsRequest->SendCancelDNSRequest(mDnsRequest->mHost,
+                                        mDnsRequest->mType,
                                         mDnsRequest->mOriginAttributes,
                                         mDnsRequest->mFlags,
                                         mReasonForCancel);
     }
     return NS_OK;
   }
 private:
   RefPtr<DNSRequestChild> mDnsRequest;
   nsresult mReasonForCancel;
 };
 
 //-----------------------------------------------------------------------------
 // DNSRequestChild
 //-----------------------------------------------------------------------------
 
-DNSRequestChild::DNSRequestChild(const nsACString& aHost,
+DNSRequestChild::DNSRequestChild(const nsACString &aHost,
+                                 const uint16_t &aType,
                                  const OriginAttributes& aOriginAttributes,
-                                 const uint32_t& aFlags,
+                                 const uint32_t &aFlags,
                                  nsIDNSListener *aListener,
                                  nsIEventTarget *target)
   : mListener(aListener)
   , mTarget(target)
   , mResultStatus(NS_OK)
   , mHost(aHost)
+  , mType(aType)
   , mOriginAttributes(aOriginAttributes)
   , mFlags(aFlags)
   , mIPCOpen(false)
 {
 }
 
 void
 DNSRequestChild::StartRequest()
@@ -243,53 +286,78 @@ DNSRequestChild::StartRequest()
 
 void
 DNSRequestChild::CallOnLookupComplete()
 {
   MOZ_ASSERT(mListener);
   mListener->OnLookupComplete(this, mResultRecord, mResultStatus);
 }
 
+void
+DNSRequestChild::CallOnLookupByTypeComplete()
+{
+  MOZ_ASSERT(mListener);
+  MOZ_ASSERT(mType != nsIDNSService::RESOLVE_TYPE_DEFAULT);
+  mListener->OnLookupByTypeComplete(this, mResultByTypeRecords, mResultStatus);
+}
+
 mozilla::ipc::IPCResult
 DNSRequestChild::RecvLookupCompleted(const DNSRequestResponse& reply)
 {
   mIPCOpen = false;
   MOZ_ASSERT(mListener);
 
   switch (reply.type()) {
   case DNSRequestResponse::TDNSRecord: {
     mResultRecord = new ChildDNSRecord(reply.get_DNSRecord(), mFlags);
     break;
   }
   case DNSRequestResponse::Tnsresult: {
     mResultStatus = reply.get_nsresult();
     break;
   }
+  case DNSRequestResponse::TArrayOfnsCString: {
+    MOZ_ASSERT(mType != nsIDNSService::RESOLVE_TYPE_DEFAULT);
+    mResultByTypeRecords = new ChildDNSByTypeRecord(reply.get_ArrayOfnsCString());
+    break;
+  }
   default:
     MOZ_ASSERT_UNREACHABLE("unknown type");
     return IPC_FAIL_NO_REASON(this);
   }
 
   MOZ_ASSERT(NS_IsMainThread());
 
   bool targetIsMain = false;
   if (!mTarget) {
     targetIsMain = true;
   } else {
     mTarget->IsOnCurrentThread(&targetIsMain);
   }
 
   if (targetIsMain) {
-    CallOnLookupComplete();
+    if (mType == nsIDNSService::RESOLVE_TYPE_DEFAULT) {
+      CallOnLookupComplete();
+    } else {
+      CallOnLookupByTypeComplete();
+    }
   } else {
-    nsCOMPtr<nsIRunnable> event =
-      NewRunnableMethod("net::DNSRequestChild::CallOnLookupComplete",
-                        this,
-                        &DNSRequestChild::CallOnLookupComplete);
-    mTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+    if (mType == nsIDNSService::RESOLVE_TYPE_DEFAULT) {
+      nsCOMPtr<nsIRunnable> event =
+        NewRunnableMethod("net::DNSRequestChild::CallOnLookupComplete",
+                          this,
+                          &DNSRequestChild::CallOnLookupComplete);
+      mTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+    } else {
+      nsCOMPtr<nsIRunnable> event =
+        NewRunnableMethod("net::DNSRequestChild::CallOnLookupByTypeComplete",
+                          this,
+                          &DNSRequestChild::CallOnLookupByTypeComplete);
+      mTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+    }
   }
 
   Unused << Send__delete__(this);
 
   return IPC_OK();
 }
 
 void
--- a/netwerk/dns/DNSRequestChild.h
+++ b/netwerk/dns/DNSRequestChild.h
@@ -6,57 +6,67 @@
 
 #ifndef mozilla_net_DNSRequestChild_h
 #define mozilla_net_DNSRequestChild_h
 
 #include "mozilla/net/PDNSRequestChild.h"
 #include "nsICancelable.h"
 #include "nsIDNSRecord.h"
 #include "nsIDNSListener.h"
+#include "nsIDNSByTypeRecord.h"
 #include "nsIEventTarget.h"
 
 namespace mozilla {
 namespace net {
 
 class DNSRequestChild final
   : public PDNSRequestChild
   , public nsICancelable
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSICANCELABLE
 
   DNSRequestChild(const nsACString& aHost,
+                  const uint16_t& aType,
                   const OriginAttributes& aOriginAttributes,
                   const uint32_t& aFlags,
                   nsIDNSListener *aListener, nsIEventTarget *target);
 
   void AddIPDLReference() {
     AddRef();
   }
   void ReleaseIPDLReference();
 
   // Sends IPDL request to parent
   void StartRequest();
   void CallOnLookupComplete();
+  void CallOnLookupByTypeComplete();
 
 protected:
   friend class CancelDNSRequestEvent;
   friend class ChildDNSService;
   virtual ~DNSRequestChild() {}
 
   virtual mozilla::ipc::IPCResult RecvLookupCompleted(const DNSRequestResponse& reply) override;
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
-  nsCOMPtr<nsIDNSListener>  mListener;
-  nsCOMPtr<nsIEventTarget>  mTarget;
-  nsCOMPtr<nsIDNSRecord>    mResultRecord;
-  nsresult                  mResultStatus;
-  nsCString                 mHost;
-  const OriginAttributes    mOriginAttributes;
-  uint16_t                  mFlags;
-  bool                      mIPCOpen;
+  nsCOMPtr<nsIDNSListener>     mListener;
+  nsCOMPtr<nsIEventTarget>     mTarget;
+  nsCOMPtr<nsIDNSRecord>       mResultRecord;
+  nsCOMPtr<nsIDNSByTypeRecord> mResultByTypeRecords; // the result of a by-type
+                                                     // query (mType must not be
+                                                     // equal to
+                                                     // nsIDNSService::RESOLVE_TYPE_DEFAULT
+                                                     // (this is reserved for
+                                                     // the standard A/AAAA query)).
+  nsresult                     mResultStatus;
+  nsCString                    mHost;
+  uint16_t                     mType;
+  const OriginAttributes       mOriginAttributes;
+  uint16_t                     mFlags;
+  bool                         mIPCOpen;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // mozilla_net_DNSRequestChild_h
--- a/netwerk/dns/DNSRequestParent.cpp
+++ b/netwerk/dns/DNSRequestParent.cpp
@@ -45,26 +45,33 @@ DNSRequestParent::DoAsyncResolve(const n
   if (NS_FAILED(rv) && !mIPCClosed) {
     mIPCClosed = true;
     Unused << SendLookupCompleted(DNSRequestResponse(rv));
   }
 }
 
 mozilla::ipc::IPCResult
 DNSRequestParent::RecvCancelDNSRequest(const nsCString& hostName,
+                                       const uint16_t& type,
                                        const OriginAttributes& originAttributes,
                                        const uint32_t& flags,
                                        const nsresult& reason)
 {
   nsresult rv;
   nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
   if (NS_SUCCEEDED(rv)) {
-    rv = dns->CancelAsyncResolveNative(hostName, flags,
-                                       this, reason,
-                                       originAttributes);
+    if (type == nsIDNSService::RESOLVE_TYPE_DEFAULT) {
+      rv = dns->CancelAsyncResolveNative(hostName, flags,
+                                         this, reason,
+                                         originAttributes);
+    } else {
+      rv = dns->CancelAsyncResolveByTypeNative(hostName, type, flags,
+                                               this, reason,
+                                               originAttributes);
+    }
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 DNSRequestParent::Recv__delete__()
 {
   mIPCClosed = true;
@@ -119,12 +126,31 @@ DNSRequestParent::OnLookupComplete(nsICa
   } else {
     Unused << SendLookupCompleted(DNSRequestResponse(status));
   }
 
   mIPCClosed = true;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+DNSRequestParent::OnLookupByTypeComplete(nsICancelable      *aRequest,
+                                         nsIDNSByTypeRecord *aRes,
+                                         nsresult            aStatus)
+{
+  if (mIPCClosed) {
+    // nothing to do: child probably crashed
+    return NS_OK;
+  }
 
+  if (NS_SUCCEEDED(aStatus)) {
+    nsTArray<nsCString> rec;
+    aRes->GetRecords(rec);
+    Unused << SendLookupCompleted(DNSRequestResponse(rec));
+  } else {
+    Unused << SendLookupCompleted(DNSRequestResponse(aStatus));
+  }
+  mIPCClosed = true;
+  return NS_OK;
+}
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/dns/DNSRequestParent.h
+++ b/netwerk/dns/DNSRequestParent.h
@@ -26,16 +26,17 @@ public:
 
   void DoAsyncResolve(const nsACString  &hostname,
                       const OriginAttributes &originAttributes,
                       uint32_t flags);
 
   // Pass args here rather than storing them in the parent; they are only
   // needed if the request is to be canceled.
   mozilla::ipc::IPCResult RecvCancelDNSRequest(const nsCString& hostName,
+                                               const uint16_t& type,
                                                const OriginAttributes& originAttributes,
                                                const uint32_t& flags,
                                                const nsresult& reason) override;
   mozilla::ipc::IPCResult Recv__delete__() override;
 
 protected:
   virtual void ActorDestroy(ActorDestroyReason why) override;
 private:
--- a/netwerk/dns/PDNSRequest.ipdl
+++ b/netwerk/dns/PDNSRequest.ipdl
@@ -20,17 +20,18 @@ async protocol PDNSRequest
 {
   manager PNecko;
 
 parent:
   // constructor in PNecko takes AsyncResolve args that initialize request
 
   // Pass args here rather than storing them in the parent; they are only
   // needed if the request is to be canceled.
-  async CancelDNSRequest(nsCString hostName, OriginAttributes originAttributes,
+  async CancelDNSRequest(nsCString hostName, uint16_t type,
+                         OriginAttributes originAttributes,
                          uint32_t flags, nsresult reason);
   async __delete__();
 
 child:
   async LookupCompleted(DNSRequestResponse reply);
 
 };
 
--- a/netwerk/dns/PDNSRequestParams.ipdlh
+++ b/netwerk/dns/PDNSRequestParams.ipdlh
@@ -18,14 +18,15 @@ struct DNSRecord
 {
   nsCString canonicalName;
   NetAddrArray addrs;
 };
 
 union DNSRequestResponse
 {
   DNSRecord;
+  nsCString[];   // The result of a by-type query
   nsresult;   // if error
 };
 
 
 } // namespace ipc
 } // namespace mozilla
--- a/netwerk/dns/TRR.cpp
+++ b/netwerk/dns/TRR.cpp
@@ -163,17 +163,18 @@ TRR::Run()
 }
 
 nsresult
 TRR::SendHTTPRequest()
 {
   // This is essentially the "run" method - created from nsHostResolver
   MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
 
-  if ((mType != TRRTYPE_A) && (mType != TRRTYPE_AAAA) && (mType != TRRTYPE_NS)) {
+  if ((mType != TRRTYPE_A) && (mType != TRRTYPE_AAAA) && (mType != TRRTYPE_NS) &&
+      (mType != TRRTYPE_TXT)) {
     // limit the calling interface because nsHostResolver has explicit slots for
     // these types
     return NS_ERROR_FAILURE;
   }
 
   if ((mType == TRRTYPE_A) || (mType == TRRTYPE_AAAA)) {
     // let NS resolves skip the blacklist check
     if (gTRRService->IsTRRBlacklisted(mHost, mPB, true)) {
@@ -758,16 +759,44 @@ TRR::DohDecode(nsCString &aHost)
             LOG(("TRR::DohDecode empty CNAME for host %s!\n",
                  host.get()));
           }
         }
         else {
           LOG(("TRR::DohDecode CNAME - ignoring another entry\n"));
         }
         break;
+      case TRRTYPE_TXT:
+      {
+        // TXT record RRDATA sections are a series of character-strings
+        // each character string is a length byte followed by that many data bytes
+        nsAutoCString txt;
+        unsigned int txtIndex = index;
+        uint16_t available = RDLENGTH;
+
+        while (available > 0) {
+          uint8_t characterStringLen = mResponse[txtIndex++];
+          available--;
+          if (characterStringLen > available) {
+            LOG(("TRR::DohDecode MALFORMED TXT RECORD\n"));
+            break;
+          }
+          txt.Append((const char *)(&mResponse[txtIndex]), characterStringLen);
+          txtIndex += characterStringLen;
+          available -= characterStringLen;
+        }
+
+        mTxt.AppendElement(txt);
+        if (mTxtTtl > TTL) {
+          mTxtTtl = TTL;
+        }
+        LOG(("TRR::DohDecode TXT host %s => %s\n",
+             host.get(), txt.get()));
+        break;
+      }
       default:
         // skip unknown record types
         LOG(("TRR unsupported TYPE (%u) RDLENGTH %u\n", TYPE, RDLENGTH));
         break;
       }
     }
     else {
       LOG(("TRR asked for %s data but got %s\n", aHost.get(), qname.get()));
@@ -843,77 +872,90 @@ TRR::DohDecode(nsCString &aHost)
   if (index != mBodySize) {
     LOG(("DohDecode failed to parse entire response body, %u out of %u bytes\n",
          index, mBodySize));
     // failed to parse 100%, do not continue
     return NS_ERROR_ILLEGAL_VALUE;
   }
 
   if ((mType != TRRTYPE_NS) && mCname.IsEmpty() &&
-      !mDNS.mAddresses.getFirst()) {
+      !mDNS.mAddresses.getFirst() &&
+      mTxt.IsEmpty()) {
     // no entries were stored!
     LOG(("TRR: No entries were stored!\n"));
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 nsresult
 TRR::ReturnData()
 {
-  // create and populate an AddrInfo instance to pass on
-  nsAutoPtr<AddrInfo> ai(new AddrInfo(mHost, mType));
-  DOHaddr *item;
-  uint32_t ttl = AddrInfo::NO_TTL_DATA;
-  while ((item = static_cast<DOHaddr*>(mDNS.mAddresses.popFirst()))) {
-    PRNetAddr prAddr;
-    NetAddrToPRNetAddr(&item->mNet, &prAddr);
-    auto *addrElement = new NetAddrElement(&prAddr);
-    ai->AddAddress(addrElement);
-    if (item->mTtl < ttl) {
-      // While the DNS packet might return individual TTLs for each address,
-      // we can only return one value in the AddrInfo class so pick the
-      // lowest number.
-      ttl = item->mTtl;
+  if (mType != TRRTYPE_TXT) {
+    // create and populate an AddrInfo instance to pass on
+    nsAutoPtr<AddrInfo> ai(new AddrInfo(mHost, mType));
+    DOHaddr *item;
+    uint32_t ttl = AddrInfo::NO_TTL_DATA;
+    while ((item = static_cast<DOHaddr*>(mDNS.mAddresses.popFirst()))) {
+      PRNetAddr prAddr;
+      NetAddrToPRNetAddr(&item->mNet, &prAddr);
+      auto *addrElement = new NetAddrElement(&prAddr);
+      ai->AddAddress(addrElement);
+      if (item->mTtl < ttl) {
+        // While the DNS packet might return individual TTLs for each address,
+        // we can only return one value in the AddrInfo class so pick the
+        // lowest number.
+        ttl = item->mTtl;
+      }
     }
+    ai->ttl = ttl;
+    if (!mHostResolver) {
+      return NS_ERROR_FAILURE;
+    }
+    (void)mHostResolver->CompleteLookup(mRec, NS_OK, ai.forget(), mPB);
+    mHostResolver = nullptr;
+    mRec = nullptr;
+  } else {
+    (void)mHostResolver->CompleteLookupByType(mRec, NS_OK, &mTxt, mTxtTtl, mPB);
   }
-  ai->ttl = ttl;
-  if (!mHostResolver) {
-    return NS_ERROR_FAILURE;
-  }
-  (void)mHostResolver->CompleteLookup(mRec, NS_OK, ai.forget(), mPB);
-  mHostResolver = nullptr;
-  mRec = nullptr;
   return NS_OK;
 }
 
 nsresult
 TRR::FailData(nsresult error)
 {
   if (!mHostResolver) {
     return NS_ERROR_FAILURE;
   }
-  // create and populate an TRR AddrInfo instance to pass on to signal that
-  // this comes from TRR
-  AddrInfo *ai = new AddrInfo(mHost, mType);
 
-  (void)mHostResolver->CompleteLookup(mRec, error, ai, mPB);
+  if (mType == TRRTYPE_TXT) {
+    (void)mHostResolver->CompleteLookupByType(mRec, error,
+                                              nullptr, 0, mPB);
+  } else {
+    // create and populate an TRR AddrInfo instance to pass on to signal that
+    // this comes from TRR
+    AddrInfo *ai = new AddrInfo(mHost, mType);
+
+    (void)mHostResolver->CompleteLookup(mRec, error, ai, mPB);
+  }
+
   mHostResolver = nullptr;
   mRec = nullptr;
   return NS_OK;
 }
 
 nsresult
 TRR::On200Response()
 {
   // decode body and create an AddrInfo struct for the response
   nsresult rv = DohDecode(mHost);
 
   if (NS_SUCCEEDED(rv)) {
-    if (!mDNS.mAddresses.getFirst() && !mCname.IsEmpty()) {
+    if (!mDNS.mAddresses.getFirst() && !mCname.IsEmpty() &&
+        mType != TRRTYPE_TXT) {
       nsCString cname = mCname;
       LOG(("TRR: check for CNAME record for %s within previous response\n",
            cname.get()));
       rv = DohDecode(cname);
       if (NS_SUCCEEDED(rv) && mDNS.mAddresses.getFirst()) {
         LOG(("TRR: Got the CNAME record without asking for it\n"));
         ReturnData();
         return NS_OK;
--- a/netwerk/dns/TRR.h
+++ b/netwerk/dns/TRR.h
@@ -15,16 +15,17 @@
 namespace mozilla { namespace net {
 
 // the values map to RFC1035 type identifiers
 enum TrrType {
   TRRTYPE_A = 1,
   TRRTYPE_NS = 2,
   TRRTYPE_CNAME = 5,
   TRRTYPE_AAAA = 28,
+  TRRTYPE_TXT = 16,
 };
 
 class DOHaddr : public LinkedListElement<DOHaddr> {
 public:
   NetAddr mNet;
   uint32_t mTtl;
 };
 
@@ -73,16 +74,17 @@ public:
     : mozilla::Runnable("TRR")
     , mRec(aRec)
     , mHostResolver(aResolver)
     , mType(aType)
     , mBodySize(0)
     , mFailed(false)
     , mCnameLoop(kCnameChaseMax)
     , mAllowRFC1918(false)
+    , mTxtTtl(UINT32_MAX)
   {
     mHost = aRec->host;
     mPB = aRec->pb;
   }
 
   // when following CNAMEs
   explicit TRR(AHostResolver *aResolver,
                nsHostRecord *aRec,
@@ -95,46 +97,49 @@ public:
     , mRec(aRec)
     , mHostResolver(aResolver)
     , mType(aType)
     , mBodySize(0)
     , mFailed(false)
     , mPB(aPB)
     , mCnameLoop(aLoopCount)
     , mAllowRFC1918(false)
+    , mTxtTtl(UINT32_MAX)
   {
 
   }
 
   // used on push
   explicit TRR(AHostResolver *aResolver, bool aPB)
     : mozilla::Runnable("TRR")
     , mHostResolver(aResolver)
     , mType(TRRTYPE_A)
     , mBodySize(0)
     , mFailed(false)
     , mPB(aPB)
     , mCnameLoop(kCnameChaseMax)
     , mAllowRFC1918(false)
+    , mTxtTtl(UINT32_MAX)
   { }
 
   // to verify a domain
   explicit TRR(AHostResolver *aResolver,
                nsACString &aHost,
                enum TrrType aType,
                bool aPB)
     : mozilla::Runnable("TRR")
     , mHost(aHost)
     , mHostResolver(aResolver)
     , mType(aType)
     , mBodySize(0)
     , mFailed(false)
     , mPB(aPB)
     , mCnameLoop(kCnameChaseMax)
     , mAllowRFC1918(false)
+    , mTxtTtl(UINT32_MAX)
   { }
 
   NS_IMETHOD Run() override;
   void Cancel();
   enum TrrType Type() { return mType; }
   nsCString mHost;
   RefPtr<nsHostRecord> mRec;
   RefPtr<AHostResolver> mHostResolver;
@@ -167,14 +172,16 @@ private:
   unsigned int mBodySize;
   bool mFailed;
   bool mPB;
   DOHresp mDNS;
   nsCOMPtr<nsITimer> mTimeout;
   nsCString mCname;
   uint32_t mCnameLoop; // loop detection counter
   bool mAllowRFC1918;
+  nsTArray<nsCString> mTxt;
+  uint32_t mTxtTtl;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // include guard
--- a/netwerk/dns/TRRService.cpp
+++ b/netwerk/dns/TRRService.cpp
@@ -634,12 +634,21 @@ TRRService::CompleteLookup(nsHostRecord 
     LOG(("TRR verified %s to be fine!\n", newRRSet->mHostName.get()));
   } else {
     LOG(("TRR says %s doesn't resolve as NS!\n", newRRSet->mHostName.get()));
     TRRBlacklist(newRRSet->mHostName, pb, false);
   }
   return LOOKUP_OK;
 }
 
+AHostResolver::LookupStatus
+TRRService::CompleteLookupByType(nsHostRecord *, nsresult,
+                                 const nsTArray<nsCString> *aResult,
+                                 uint32_t aTtl,
+                                 bool aPb)
+{
+  return LOOKUP_OK;
+}
+
 #undef LOG
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/dns/TRRService.h
+++ b/netwerk/dns/TRRService.h
@@ -39,16 +39,17 @@ public:
   bool EarlyAAAA() { return mEarlyAAAA; }
   bool DisableIPv6() { return mDisableIPv6; }
   bool DisableECS() { return mDisableECS; }
   nsresult GetURI(nsCString &result);
   nsresult GetCredentials(nsCString &result);
   uint32_t GetRequestTimeout() { return mTRRTimeout; }
 
   LookupStatus CompleteLookup(nsHostRecord *, nsresult, mozilla::net::AddrInfo *, bool pb) override;
+  LookupStatus CompleteLookupByType(nsHostRecord *, nsresult, const nsTArray<nsCString> *, uint32_t, bool pb) override;
   void TRRBlacklist(const nsACString &host, bool privateBrowsing, bool aParentsToo);
   bool IsTRRBlacklisted(const nsACString &host, bool privateBrowsing, bool fullhost);
 
   bool MaybeBootstrap(const nsACString &possible, nsACString &result);
 
 private:
   virtual  ~TRRService();
   nsresult ReadPrefs(const char *name);
--- a/netwerk/dns/moz.build
+++ b/netwerk/dns/moz.build
@@ -7,16 +7,17 @@
 with Files('**'):
     BUG_COMPONENT = ('Core', 'Networking: DNS')
 
 DIRS += [
     'mdns',
 ]
 
 XPIDL_SOURCES += [
+    'nsIDNSByTypeRecord.idl',
     'nsIDNSListener.idl',
     'nsIDNSRecord.idl',
     'nsIDNSService.idl',
     'nsIEffectiveTLDService.idl',
     'nsIIDNService.idl',
     'nsPIDNSService.idl',
 ]
 
--- a/netwerk/dns/nsDNSService2.cpp
+++ b/netwerk/dns/nsDNSService2.cpp
@@ -2,16 +2,17 @@
 /* vim: set sw=4 ts=8 et tw=80 : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsDNSService2.h"
 #include "nsIDNSRecord.h"
 #include "nsIDNSListener.h"
+#include "nsIDNSByTypeRecord.h"
 #include "nsICancelable.h"
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 #include "nsIServiceManager.h"
 #include "nsIXPConnect.h"
 #include "nsProxyRelease.h"
 #include "nsReadableUtils.h"
 #include "nsString.h"
@@ -301,74 +302,126 @@ nsDNSRecord::ReportUnusable(uint16_t aPo
         mIterGenCnt == mHostRecord->addr_info_gencnt &&
         mIter) {
         mHostRecord->ReportUnusable(&mIter->mAddress);
     }
 
     return NS_OK;
 }
 
+class nsDNSByTypeRecord : public nsIDNSByTypeRecord
+{
+public:
+    NS_DECL_THREADSAFE_ISUPPORTS
+    NS_DECL_NSIDNSBYTYPERECORD
+
+    explicit nsDNSByTypeRecord(nsHostRecord *hostRecord)
+      : mHostRecord(hostRecord)
+    {}
+
+private:
+    virtual ~nsDNSByTypeRecord() = default;
+    RefPtr<nsHostRecord>  mHostRecord;
+};
+
+NS_IMPL_ISUPPORTS(nsDNSByTypeRecord, nsIDNSByTypeRecord)
+
+NS_IMETHODIMP
+nsDNSByTypeRecord::GetRecords(nsTArray<nsCString> &aRecords)
+{
+   // deep copy
+   MutexAutoLock lock(mHostRecord->mRequestByTypeResultLock);
+   aRecords = mHostRecord->mRequestByTypeResult;
+   return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSByTypeRecord::GetRecordsAsOneString(nsACString &aRecords)
+{
+  // deep copy
+  MutexAutoLock lock(mHostRecord->mRequestByTypeResultLock);
+
+  for (uint32_t i = 0; i < mHostRecord->mRequestByTypeResult.Length(); i++) {
+    aRecords.Append(mHostRecord->mRequestByTypeResult[i]);
+  }
+  return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 
 class nsDNSAsyncRequest final : public nsResolveHostCallback
                               , public nsICancelable
 {
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSICANCELABLE
 
     nsDNSAsyncRequest(nsHostResolver   *res,
                       const nsACString &host,
+                      uint16_t          type,
                       const OriginAttributes &attrs,
                       nsIDNSListener   *listener,
                       uint16_t          flags,
                       uint16_t          af)
         : mResolver(res)
         , mHost(host)
+        , mType(type)
         , mOriginAttributes(attrs)
         , mListener(listener)
         , mFlags(flags)
         , mAF(af)
     { }
 
     void OnResolveHostComplete(nsHostResolver *, nsHostRecord *, nsresult) override;
     // Returns TRUE if the DNS listener arg is the same as the member listener
     // Used in Cancellations to remove DNS requests associated with a
     // particular hostname and nsIDNSListener
     bool EqualsAsyncListener(nsIDNSListener *aListener) override;
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const override;
 
     RefPtr<nsHostResolver> mResolver;
     nsCString                mHost; // hostname we're resolving
+    uint16_t                 mType;
     const OriginAttributes   mOriginAttributes; // The originAttributes for this resolving
     nsCOMPtr<nsIDNSListener> mListener;
     uint16_t                 mFlags;
     uint16_t                 mAF;
 private:
     virtual ~nsDNSAsyncRequest() = default;
 };
 
 NS_IMPL_ISUPPORTS(nsDNSAsyncRequest, nsICancelable)
 
 void
 nsDNSAsyncRequest::OnResolveHostComplete(nsHostResolver *resolver,
                                          nsHostRecord   *hostRecord,
                                          nsresult        status)
 {
-    // need to have an owning ref when we issue the callback to enable
-    // the caller to be able to addref/release multiple times without
-    // destroying the record prematurely.
-    nsCOMPtr<nsIDNSRecord> rec;
-    if (NS_SUCCEEDED(status)) {
-        NS_ASSERTION(hostRecord, "no host record");
-        rec = new nsDNSRecord(hostRecord);
+    if (hostRecord->type != nsDNSService::RESOLVE_TYPE_DEFAULT) {
+        nsCOMPtr<nsIDNSByTypeRecord> rec;
+        if (NS_SUCCEEDED(status)) {
+            MOZ_ASSERT(hostRecord, "no host record");
+            rec = new nsDNSByTypeRecord(hostRecord);
+        }
+        mListener->OnLookupByTypeComplete(this,
+                                          rec,
+                                          status);
+    } else {
+        // need to have an owning ref when we issue the callback to enable
+        // the caller to be able to addref/release multiple times without
+        // destroying the record prematurely.
+        nsCOMPtr<nsIDNSRecord> rec;
+        if (NS_SUCCEEDED(status)) {
+            NS_ASSERTION(hostRecord, "no host record");
+            rec = new nsDNSRecord(hostRecord);
+        }
+
+        mListener->OnLookupComplete(this, rec, status);
     }
-
-    mListener->OnLookupComplete(this, rec, status);
     mListener = nullptr;
 }
 
 bool
 nsDNSAsyncRequest::EqualsAsyncListener(nsIDNSListener *aListener)
 {
     nsCOMPtr<nsIDNSListenerProxy> wrapper = do_QueryInterface(mListener);
     if (wrapper) {
@@ -391,17 +444,17 @@ nsDNSAsyncRequest::SizeOfIncludingThis(M
 
     return n;
 }
 
 NS_IMETHODIMP
 nsDNSAsyncRequest::Cancel(nsresult reason)
 {
     NS_ENSURE_ARG(NS_FAILED(reason));
-    mResolver->DetachCallback(mHost, mOriginAttributes, mFlags, mAF,
+    mResolver->DetachCallback(mHost, mType, mOriginAttributes, mFlags, mAF,
                               this, reason);
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 
 class nsDNSSyncRequest
     : public nsResolveHostCallback
@@ -794,46 +847,24 @@ nsDNSService::PreprocessHostname(bool   
     }
 
     if (!(IsUTF8(aInput) && NS_SUCCEEDED(aIDN->ConvertUTF8toACE(aInput, aACE)))) {
         return NS_ERROR_FAILURE;
     }
     return NS_OK;
 }
 
-NS_IMETHODIMP
-nsDNSService::AsyncResolve(const nsACString  &aHostname,
-                           uint32_t           flags,
-                           nsIDNSListener    *listener,
-                           nsIEventTarget    *target_,
-                           JS::HandleValue    aOriginAttributes,
-                           JSContext         *aCx,
-                           uint8_t            aArgc,
-                           nsICancelable    **result)
-{
-    OriginAttributes attrs;
-
-    if (aArgc == 1) {
-        if (!aOriginAttributes.isObject() ||
-            !attrs.Init(aCx, aOriginAttributes)) {
-            return NS_ERROR_INVALID_ARG;
-        }
-    }
-
-    return AsyncResolveNative(aHostname, flags, listener,
-                              target_, attrs, result);
-}
-
-NS_IMETHODIMP
-nsDNSService::AsyncResolveNative(const nsACString        &aHostname,
-                                 uint32_t                 flags,
-                                 nsIDNSListener          *aListener,
-                                 nsIEventTarget          *target_,
-                                 const OriginAttributes  &aOriginAttributes,
-                                 nsICancelable          **result)
+nsresult
+nsDNSService::AsyncResolveInternal(const nsACString        &aHostname,
+                                   uint16_t                 type,
+                                   uint32_t                 flags,
+                                   nsIDNSListener          *aListener,
+                                   nsIEventTarget          *target_,
+                                   const OriginAttributes  &aOriginAttributes,
+                                   nsICancelable          **result)
 {
     // grab reference to global host resolver and IDN service.  beware
     // simultaneous shutdown!!
     RefPtr<nsHostResolver> res;
     nsCOMPtr<nsIIDNService> idn;
     nsCOMPtr<nsIEventTarget> target = target_;
     nsCOMPtr<nsIDNSListener> listener = aListener;
     bool localDomain = false;
@@ -850,16 +881,20 @@ nsDNSService::AsyncResolveNative(const n
 
     if (mNotifyResolution) {
         NS_DispatchToMainThread(new NotifyDNSResolution(aHostname));
     }
 
     if (!res)
         return NS_ERROR_OFFLINE;
 
+    if ((type != RESOLVE_TYPE_DEFAULT) && (type != RESOLVE_TYPE_TXT)) {
+        return NS_ERROR_INVALID_ARG;
+    }
+
     nsCString hostname;
     nsresult rv = PreprocessHostname(localDomain, aHostname, idn, hostname);
     if (NS_FAILED(rv)) {
         return rv;
     }
 
     if (GetOffline() &&
         (!mOfflineLocalhost || !hostname.LowerCaseEqualsASCII("localhost"))) {
@@ -871,57 +906,38 @@ nsDNSService::AsyncResolveNative(const n
     if (wrappedListener && !target) {
         target = GetMainThreadEventTarget();
     }
 
     if (target) {
         listener = new DNSListenerProxy(listener, target);
     }
 
-    uint16_t af = GetAFForLookup(hostname, flags);
+    uint16_t af = (type != RESOLVE_TYPE_DEFAULT) ? 0
+                                                 : GetAFForLookup(hostname, flags);
 
     MOZ_ASSERT(listener);
     RefPtr<nsDNSAsyncRequest> req =
-        new nsDNSAsyncRequest(res, hostname, aOriginAttributes, listener, flags, af);
+        new nsDNSAsyncRequest(res, hostname, type, aOriginAttributes, listener,
+                              flags, af);
     if (!req)
         return NS_ERROR_OUT_OF_MEMORY;
 
-    rv = res->ResolveHost(req->mHost, req->mOriginAttributes, flags, af, req);
+    rv = res->ResolveHost(req->mHost, type, req->mOriginAttributes, flags, af, req);
     req.forget(result);
     return rv;
 }
 
-NS_IMETHODIMP
-nsDNSService::CancelAsyncResolve(const nsACString &aHostname,
-                                 uint32_t          aFlags,
-                                 nsIDNSListener   *aListener,
-                                 nsresult          aReason,
-                                 JS::HandleValue   aOriginAttributes,
-                                 JSContext        *aCx,
-                                 uint8_t           aArgc)
-{
-    OriginAttributes attrs;
-
-    if (aArgc == 1) {
-        if (!aOriginAttributes.isObject() ||
-            !attrs.Init(aCx, aOriginAttributes)) {
-            return NS_ERROR_INVALID_ARG;
-        }
-    }
-
-    return CancelAsyncResolveNative(aHostname, aFlags,
-                                    aListener, aReason, attrs);
-}
-
-NS_IMETHODIMP
-nsDNSService::CancelAsyncResolveNative(const nsACString       &aHostname,
-                                       uint32_t                aFlags,
-                                       nsIDNSListener         *aListener,
-                                       nsresult                aReason,
-                                       const OriginAttributes &aOriginAttributes)
+nsresult
+nsDNSService::CancelAsyncResolveInternal(const nsACString       &aHostname,
+                                         uint16_t                aType,
+                                         uint32_t                aFlags,
+                                         nsIDNSListener         *aListener,
+                                         nsresult                aReason,
+                                         const OriginAttributes &aOriginAttributes)
 {
     // grab reference to global host resolver and IDN service.  beware
     // simultaneous shutdown!!
     RefPtr<nsHostResolver> res;
     nsCOMPtr<nsIIDNService> idn;
     bool localDomain = false;
     {
         MutexAutoLock lock(mLock);
@@ -937,21 +953,162 @@ nsDNSService::CancelAsyncResolveNative(c
         return NS_ERROR_OFFLINE;
 
     nsCString hostname;
     nsresult rv = PreprocessHostname(localDomain, aHostname, idn, hostname);
     if (NS_FAILED(rv)) {
         return rv;
     }
 
-    uint16_t af = GetAFForLookup(hostname, aFlags);
+    uint16_t af = (aType != RESOLVE_TYPE_DEFAULT) ? 0
+                                                  : GetAFForLookup(hostname, aFlags);
+
+    res->CancelAsyncRequest(hostname, aType, aOriginAttributes, aFlags,
+                            af, aListener, aReason);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::AsyncResolve(const nsACString  &aHostname,
+                           uint32_t           flags,
+                           nsIDNSListener    *listener,
+                           nsIEventTarget    *target_,
+                           JS::HandleValue    aOriginAttributes,
+                           JSContext         *aCx,
+                           uint8_t            aArgc,
+                           nsICancelable    **result)
+{
+    OriginAttributes attrs;
+
+    if (aArgc == 1) {
+        if (!aOriginAttributes.isObject() ||
+            !attrs.Init(aCx, aOriginAttributes)) {
+            return NS_ERROR_INVALID_ARG;
+        }
+    }
+
+    return AsyncResolveInternal(aHostname, RESOLVE_TYPE_DEFAULT, flags, listener,
+                                target_, attrs, result);
+}
+
+NS_IMETHODIMP
+nsDNSService::AsyncResolveNative(const nsACString        &aHostname,
+                                 uint32_t                 flags,
+                                 nsIDNSListener          *aListener,
+                                 nsIEventTarget          *target_,
+                                 const OriginAttributes  &aOriginAttributes,
+                                 nsICancelable          **result)
+{
+    return AsyncResolveInternal(aHostname, RESOLVE_TYPE_DEFAULT, flags, aListener, target_,
+                                aOriginAttributes, result);
+}
+
+NS_IMETHODIMP
+nsDNSService::AsyncResolveByType(const nsACString  &aHostname,
+                                 uint16_t           aType,
+                                 uint32_t           aFlags,
+                                 nsIDNSListener    *aListener,
+                                 nsIEventTarget    *aTarget_,
+                                 JS::HandleValue    aOriginAttributes,
+                                 JSContext         *aCx,
+                                 uint8_t            aArgc,
+                                 nsICancelable    **aResult)
+{
+    OriginAttributes attrs;
+
+    if (aArgc == 1) {
+        if (!aOriginAttributes.isObject() ||
+            !attrs.Init(aCx, aOriginAttributes)) {
+            return NS_ERROR_INVALID_ARG;
+        }
+    }
+
+    return AsyncResolveInternal(aHostname, aType, aFlags, aListener,
+                                aTarget_, attrs, aResult);
+}
 
-    res->CancelAsyncRequest(hostname, aOriginAttributes, aFlags, af,
-                            aListener, aReason);
-    return NS_OK;
+NS_IMETHODIMP
+nsDNSService::AsyncResolveByTypeNative(const nsACString       &aHostname,
+                                       uint16_t                aType,
+                                       uint32_t                aFlags,
+                                       nsIDNSListener         *aListener,
+                                       nsIEventTarget         *aTarget_,
+                                       const OriginAttributes &aOriginAttributes,
+                                       nsICancelable         **aResult)
+{
+    return AsyncResolveInternal(aHostname, aType, aFlags, aListener, aTarget_,
+                                aOriginAttributes, aResult);
+}
+
+NS_IMETHODIMP
+nsDNSService::CancelAsyncResolve(const nsACString &aHostname,
+                                 uint32_t          aFlags,
+                                 nsIDNSListener   *aListener,
+                                 nsresult          aReason,
+                                 JS::HandleValue   aOriginAttributes,
+                                 JSContext        *aCx,
+                                 uint8_t           aArgc)
+{
+    OriginAttributes attrs;
+
+    if (aArgc == 1) {
+        if (!aOriginAttributes.isObject() ||
+            !attrs.Init(aCx, aOriginAttributes)) {
+            return NS_ERROR_INVALID_ARG;
+        }
+    }
+
+    return CancelAsyncResolveInternal(aHostname, RESOLVE_TYPE_DEFAULT, aFlags,
+                                      aListener, aReason, attrs);
+}
+
+NS_IMETHODIMP
+nsDNSService::CancelAsyncResolveNative(const nsACString       &aHostname,
+                                       uint32_t                aFlags,
+                                       nsIDNSListener         *aListener,
+                                       nsresult                aReason,
+                                       const OriginAttributes &aOriginAttributes)
+{
+    return CancelAsyncResolveInternal(aHostname, RESOLVE_TYPE_DEFAULT, aFlags, aListener, aReason,
+                                      aOriginAttributes);
+}
+
+NS_IMETHODIMP
+nsDNSService::CancelAsyncResolveByType(const nsACString &aHostname,
+                                       uint16_t          aType,
+                                       uint32_t          aFlags,
+                                       nsIDNSListener   *aListener,
+                                       nsresult          aReason,
+                                       JS::HandleValue   aOriginAttributes,
+                                       JSContext        *aCx,
+                                       uint8_t           aArgc)
+{
+    OriginAttributes attrs;
+
+    if (aArgc == 1) {
+        if (!aOriginAttributes.isObject() ||
+            !attrs.Init(aCx, aOriginAttributes)) {
+            return NS_ERROR_INVALID_ARG;
+        }
+    }
+
+    return CancelAsyncResolveInternal(aHostname, aType, aFlags,
+                                      aListener, aReason, attrs);
+}
+
+NS_IMETHODIMP
+nsDNSService::CancelAsyncResolveByTypeNative(const nsACString       &aHostname,
+                                             uint16_t                aType,
+                                             uint32_t                aFlags,
+                                             nsIDNSListener         *aListener,
+                                             nsresult                aReason,
+                                             const OriginAttributes &aOriginAttributes)
+{
+    return CancelAsyncResolveInternal(aHostname, aType, aFlags, aListener, aReason,
+                                      aOriginAttributes);
 }
 
 NS_IMETHODIMP
 nsDNSService::Resolve(const nsACString  &aHostname,
                       uint32_t           flags,
                       JS::HandleValue    aOriginAttributes,
                       JSContext         *aCx,
                       uint8_t            aArgc,
@@ -1039,17 +1196,17 @@ nsDNSService::ResolveInternal(const nsAC
     if (!mon)
         return NS_ERROR_OUT_OF_MEMORY;
 
     PR_EnterMonitor(mon);
     RefPtr<nsDNSSyncRequest> syncReq = new nsDNSSyncRequest(mon);
 
     uint16_t af = GetAFForLookup(hostname, flags);
 
-    rv = res->ResolveHost(hostname, aOriginAttributes, flags, af, syncReq);
+    rv = res->ResolveHost(hostname, RESOLVE_TYPE_DEFAULT, aOriginAttributes, flags, af, syncReq);
     if (NS_SUCCEEDED(rv)) {
         // wait for result
         while (!syncReq->mDone) {
             PR_Wait(mon, PR_INTERVAL_NO_TIMEOUT);
         }
 
         if (NS_FAILED(syncReq->mStatus)) {
             rv = syncReq->mStatus;
--- a/netwerk/dns/nsDNSService2.h
+++ b/netwerk/dns/nsDNSService2.h
@@ -56,16 +56,31 @@ private:
 
     uint16_t GetAFForLookup(const nsACString &host, uint32_t flags);
 
     nsresult PreprocessHostname(bool              aLocalDomain,
                                 const nsACString &aInput,
                                 nsIIDNService    *aIDN,
                                 nsACString       &aACE);
 
+    nsresult AsyncResolveInternal(const nsACString        &aHostname,
+                                  uint16_t                 type,
+                                  uint32_t                 flags,
+                                  nsIDNSListener          *aListener,
+                                  nsIEventTarget          *target_,
+                                  const mozilla::OriginAttributes  &aOriginAttributes,
+                                  nsICancelable          **result);
+
+    nsresult CancelAsyncResolveInternal(const nsACString       &aHostname,
+                                        uint16_t                aType,
+                                        uint32_t                aFlags,
+                                        nsIDNSListener         *aListener,
+                                        nsresult                aReason,
+                                        const mozilla::OriginAttributes &aOriginAttributes);
+
     nsresult ResolveInternal(const nsACString &aHostname,
                              uint32_t flags,
                              const mozilla::OriginAttributes &aOriginAttributes,
                              nsIDNSRecord **result);
 
     RefPtr<nsHostResolver>  mResolver;
     nsCOMPtr<nsIIDNService>   mIDN;
 
--- a/netwerk/dns/nsHostResolver.cpp
+++ b/netwerk/dns/nsHostResolver.cpp
@@ -151,43 +151,45 @@ IsLowPriority(uint16_t flags)
 
 //----------------------------------------------------------------------------
 // this macro filters out any flags that are not used when constructing the
 // host key.  the significant flags are those that would affect the resulting
 // host record (i.e., the flags that are passed down to PR_GetAddrInfoByName).
 #define RES_KEY_FLAGS(_f) ((_f) & (nsHostResolver::RES_CANON_NAME |     \
                                    nsHostResolver::RES_DISABLE_TRR))
 
-nsHostKey::nsHostKey(const nsACString& aHost, uint16_t aFlags,
+nsHostKey::nsHostKey(const nsACString& aHost, uint16_t aType, uint16_t aFlags,
                      uint16_t aAf, bool aPb, const nsACString& aOriginsuffix)
   : host(aHost)
+  , type(aType)
   , flags(aFlags)
   , af(aAf)
   , pb(aPb)
   , originSuffix(aOriginsuffix)
 {
   if (TRR_DISABLED(gTRRService->Mode())) {
     // When not using TRR, lookup all answers as TRR-disabled
     flags |= nsHostResolver::RES_DISABLE_TRR;
   }
 }
 
 bool
 nsHostKey::operator==(const nsHostKey& other) const
 {
     return host == other.host &&
+        type == other.type &&
         RES_KEY_FLAGS (flags) == RES_KEY_FLAGS(other.flags) &&
         af == other.af &&
         originSuffix == other.originSuffix;
 }
 
 PLDHashNumber
 nsHostKey::Hash() const
 {
-    return AddToHash(HashString(host.get()), RES_KEY_FLAGS(flags), af,
+    return AddToHash(HashString(host.get()), type, RES_KEY_FLAGS(flags), af,
                      HashString(originSuffix.get()));
 }
 
 size_t
 nsHostKey::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
 {
     size_t n = 0;
     n += host.SizeOfExcludingThisIfUnshared(mallocSizeOf);
@@ -198,16 +200,17 @@ nsHostKey::SizeOfExcludingThis(mozilla::
 nsHostRecord::nsHostRecord(const nsHostKey& key)
     : nsHostKey(key)
     , addr_info_lock("nsHostRecord.addr_info_lock")
     , addr_info_gencnt(0)
     , addr_info(nullptr)
     , addr(nullptr)
     , negative(false)
     , mResolverMode(MODE_NATIVEONLY)
+    , mRequestByTypeResultLock("nsHostRecord.mRequestByTypeResultLock")
     , mFirstTRRresult(NS_OK)
     , mResolving(0)
     , mTRRSuccess(0)
     , mNativeSuccess(0)
     , mNative(false)
     , mTRRUsed(false)
     , mNativeUsed(false)
     , onQueue(false)
@@ -230,16 +233,20 @@ nsHostRecord::Cancel()
     if (mTrrA) {
         mTrrA->Cancel();
         mTrrA = nullptr;
     }
     if (mTrrAAAA) {
         mTrrAAAA->Cancel();
         mTrrAAAA = nullptr;
     }
+    if (mTrrTxt) {
+        mTrrTxt->Cancel();
+        mTrrTxt = nullptr;
+    }
 }
 
 void
 nsHostRecord::Invalidate()
 {
     mDoomed = true;
 }
 
@@ -646,17 +653,21 @@ nsHostResolver::Init()
 
 void
 nsHostResolver::ClearPendingQueue(LinkedList<RefPtr<nsHostRecord>>& aPendingQ)
 {
     // loop through pending queue, erroring out pending lookups.
     if (!aPendingQ.isEmpty()) {
         for (RefPtr<nsHostRecord> rec : aPendingQ) {
             rec->Cancel();
-            CompleteLookup(rec, NS_ERROR_ABORT, nullptr, rec->pb);
+            if (rec->type == nsIDNSService::RESOLVE_TYPE_DEFAULT) {
+                CompleteLookup(rec, NS_ERROR_ABORT, nullptr, rec->pb);
+            } else {
+                CompleteLookupByType(rec, NS_ERROR_ABORT, nullptr, 0, rec->pb);
+            }
         }
     }
 }
 
 //
 // FlushCache() is what we call when the network has changed. We must not
 // trust names that were resolved before this change. They may resolve
 // differently now.
@@ -774,17 +785,18 @@ nsHostResolver::Shutdown()
 
 nsresult
 nsHostResolver::GetHostRecord(const nsACString &host,
                               uint16_t flags, uint16_t af, bool pb,
                               const nsCString &originSuffix,
                               nsHostRecord **result)
 {
     MutexAutoLock lock(mLock);
-    nsHostKey key(host, flags, af, pb, originSuffix);
+    nsHostKey key(host, nsIDNSService::RESOLVE_TYPE_DEFAULT, flags, af, pb,
+                  originSuffix);
 
     RefPtr<nsHostRecord>& entry = mRecordDB.GetOrInsert(key);
     if (!entry) {
         entry = new nsHostRecord(key);
     }
 
     RefPtr<nsHostRecord> rec = entry;
     if (rec->addr) {
@@ -794,27 +806,29 @@ nsHostResolver::GetHostRecord(const nsAC
         return NS_ERROR_FAILURE;
     }
     *result = rec.forget().take();
     return NS_OK;
 }
 
 nsresult
 nsHostResolver::ResolveHost(const nsACString &aHost,
+                            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);
 
-    LOG(("Resolving host [%s]%s%s.\n", host.get(),
+    LOG(("Resolving host [%s]%s%s type %d.\n", host.get(),
          flags & RES_BYPASS_CACHE ? " - bypassing cache" : "",
-         flags & RES_REFRESH_CACHE ? " - refresh cache" : ""));
+         flags & RES_REFRESH_CACHE ? " - refresh cache" : "",
+         type));
 
     // ensure that we are working with a valid hostname before proceeding.  see
     // bug 304904 for details.
     if (!net_IsValidHostName(host))
         return NS_ERROR_UNKNOWN_HOST;
 
     RefPtr<nsResolveHostCallback> callback(aCallback);
     // if result is set inside the lock, then we need to issue the
@@ -837,85 +851,105 @@ nsHostResolver::ResolveHost(const nsACSt
             // 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.
             nsAutoCString originSuffix;
             aOriginAttributes.CreateSuffix(originSuffix);
 
-            nsHostKey key(host, flags, af,
+            nsHostKey key(host, type, flags, af,
                           (aOriginAttributes.mPrivateBrowsingId > 0),
                           originSuffix);
             RefPtr<nsHostRecord>& entry = mRecordDB.GetOrInsert(key);
             if (!entry) {
                 entry = new nsHostRecord(key);
             }
 
             RefPtr<nsHostRecord> rec = entry;
             MOZ_ASSERT(rec, "Record should not be null");
             if (!(flags & RES_BYPASS_CACHE) &&
-                     rec->HasUsableResult(TimeStamp::NowLoRes(), flags)) {
+                rec->HasUsableResult(TimeStamp::NowLoRes(), flags)) {
                 LOG(("  Using cached record for host [%s].\n", host.get()));
                 // put reference to host record on stack...
                 result = rec;
-                Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT);
+                if (type == nsIDNSService::RESOLVE_TYPE_DEFAULT) {
+                    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()));
-                    Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
-                                          METHOD_NEGATIVE_HIT);
+                    if (type == nsIDNSService::RESOLVE_TYPE_DEFAULT) {
+                        Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+                                              METHOD_NEGATIVE_HIT);
+                    }
                     status = NS_ERROR_UNKNOWN_HOST;
                 }
             } else if (rec->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;
+                if (type != nsIDNSService::RESOLVE_TYPE_DEFAULT) {
+                    // do not send a query with type for ip literals.
+                    rv = NS_ERROR_UNKNOWN_HOST;
+                } else {
+                    // 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 (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! ;-)
-                rec->addr = MakeUnique<NetAddr>();
-                PRNetAddrToNetAddr(&tempAddr, rec->addr.get());
-                // put reference to host record on stack...
-                Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
-                                      METHOD_LITERAL);
-                result = rec;
+                if (type != nsIDNSService::RESOLVE_TYPE_DEFAULT) {
+                    // do not send a query with type for ip literals.
+                    rv = NS_ERROR_UNKNOWN_HOST;
+                } else {
+                    // ok, just copy the result into the host record, and be
+                    // done with it! ;-)
+                    rec->addr = MakeUnique<NetAddr>();
+                    PRNetAddrToNetAddr(&tempAddr, rec->addr.get());
+                    // put reference to host record on stack...
+                    Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+                                          METHOD_LITERAL);
+                    result = rec;
+                }
             } 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()));
-                Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
-                                      METHOD_OVERFLOW);
+                if (type == nsIDNSService::RESOLVE_TYPE_DEFAULT) {
+                    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;
             } else if (flags & RES_OFFLINE) {
                 LOG(("  Offline request for host [%s]; ignoring.\n", host.get()));
                 rv = NS_ERROR_OFFLINE;
             } 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 (!(flags & RES_BYPASS_CACHE) &&
                     ((af == PR_AF_INET) || (af == PR_AF_INET6))) {
+                     MOZ_ASSERT(type == nsIDNSService::RESOLVE_TYPE_DEFAULT);
                     // First, search for an entry with AF_UNSPEC
-                    const nsHostKey unspecKey(host, flags, PR_AF_UNSPEC,
+                    const nsHostKey unspecKey(host,
+                                              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->addr_info || unspecRec->negative,
@@ -990,41 +1024,47 @@ nsHostResolver::ResolveHost(const nsACSt
                     if (flags & RES_REFRESH_CACHE) {
                         rec->Invalidate();
                     }
 
                     // Add callback to the list of pending callbacks.
                     rec->mCallbacks.insertBack(callback);
                     rec->flags = flags;
                     rv = NameLookup(rec);
-                    Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
-                                          METHOD_NETWORK_FIRST);
+                    if (type == nsIDNSService::RESOLVE_TYPE_DEFAULT) {
+                        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' query: callback [%p]",
                              host.get(), callback.get()));
                     }
                 }
             } else if (rec->mDidCallbacks) {
                 // 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);
+                if (type == nsIDNSService::RESOLVE_TYPE_DEFAULT) {
+                    Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_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()));
 
                 rec->mCallbacks.insertBack(callback);
                 if (rec->onQueue) {
-                    Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
-                                          METHOD_NETWORK_SHARED);
+                    if (type == nsIDNSService::RESOLVE_TYPE_DEFAULT) {
+                        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.
@@ -1054,32 +1094,33 @@ nsHostResolver::ResolveHost(const nsACSt
         callback->OnResolveHostComplete(this, result, status);
     }
 
     return rv;
 }
 
 void
 nsHostResolver::DetachCallback(const nsACString &host,
+                               uint16_t                aType,
                                const OriginAttributes &aOriginAttributes,
                                uint16_t                flags,
                                uint16_t                af,
                                nsResolveHostCallback  *aCallback,
                                nsresult                status)
 {
     RefPtr<nsHostRecord> rec;
     RefPtr<nsResolveHostCallback> callback(aCallback);
 
     {
         MutexAutoLock lock(mLock);
 
         nsAutoCString originSuffix;
         aOriginAttributes.CreateSuffix(originSuffix);
 
-        nsHostKey key(host, flags, af,
+        nsHostKey key(host, aType, flags, af,
                       (aOriginAttributes.mPrivateBrowsingId > 0),
                       originSuffix);
         RefPtr<nsHostRecord> entry = mRecordDB.Get(key);
         if (entry) {
             // walk list looking for |callback|... we cannot assume
             // that it will be there!
 
             for (nsResolveHostCallback* c: entry->mCallbacks) {
@@ -1137,17 +1178,17 @@ nsHostResolver::TrrLookup_unlocked(nsHos
 // returns error if no TRR resolve is issued
 // it is impt this is not called while a native lookup is going on
 nsresult
 nsHostResolver::TrrLookup(nsHostRecord *aRec, TRR *pushedTRR)
 {
     RefPtr<nsHostRecord> rec(aRec);
     mLock.AssertCurrentThreadOwns();
 #ifdef DEBUG
-    {
+    if (rec->type == nsIDNSService::RESOLVE_TYPE_DEFAULT) {
         MutexAutoLock trrlock(rec->mTrrLock);
         MOZ_ASSERT(!TRROutstanding());
     }
 #endif
     MOZ_ASSERT(!rec->mResolving);
 
     if (!gTRRService || !gTRRService->Enabled()) {
         LOG(("TrrLookup:: %s service not enabled\n", rec->host.get()));
@@ -1159,24 +1200,32 @@ nsHostResolver::TrrLookup(nsHostRecord *
         MOZ_ASSERT(mEvictionQSize);
         AssertOnQ(rec, mEvictionQ);
 
         rec->remove();
         mEvictionQSize--;
     }
 
     rec->mTRRSuccess = 0; // bump for each successful TRR response
-    rec->mTrrAUsed = nsHostRecord::INIT;
-    rec->mTrrAAAAUsed = nsHostRecord::INIT;
     rec->mTrrStart = TimeStamp::Now();
     rec->mTRRUsed = true; // this record gets TRR treatment
 
-    // If asking for AF_UNSPEC, issue both A and AAAA.
-    // If asking for AF_INET6 or AF_INET, do only that single type
-    enum TrrType rectype = (rec->af == AF_INET6)? TRRTYPE_AAAA : TRRTYPE_A;
+    enum TrrType rectype;
+
+    if (rec->type == nsIDNSService::RESOLVE_TYPE_DEFAULT) {
+        rec->mTrrAUsed = nsHostRecord::INIT;
+        rec->mTrrAAAAUsed = nsHostRecord::INIT;
+
+        // If asking for AF_UNSPEC, issue both A and AAAA.
+        // If asking for AF_INET6 or AF_INET, do only that single type
+        rectype = (rec->af == AF_INET6)? TRRTYPE_AAAA : TRRTYPE_A;
+    } else {
+        rectype = TRRTYPE_TXT;
+    }
+
     if (pushedTRR) {
         rectype = pushedTRR->Type();
     }
     bool sendAgain;
 
     bool madeQuery = false;
     do {
         sendAgain = false;
@@ -1192,16 +1241,19 @@ nsHostResolver::TrrLookup(nsHostRecord *
             if (rectype == TRRTYPE_A) {
                 MOZ_ASSERT(!rec->mTrrA);
                 rec->mTrrA = trr;
                 rec->mTrrAUsed = nsHostRecord::STARTED;
             } else if (rectype == TRRTYPE_AAAA) {
                 MOZ_ASSERT(!rec->mTrrAAAA);
                 rec->mTrrAAAA = trr;
                 rec->mTrrAAAAUsed = nsHostRecord::STARTED;
+            } else if (rectype == TRRTYPE_TXT) {
+                MOZ_ASSERT(!rec->mTrrTxt);
+                rec->mTrrTxt = trr;
             } else {
                 LOG(("TrrLookup called with bad type set: %d\n", rectype));
                 MOZ_ASSERT(0);
             }
             madeQuery = true;
             if (!pushedTRR && (rec->af == AF_UNSPEC) && (rectype == TRRTYPE_A)) {
                 rectype = TRRTYPE_AAAA;
                 sendAgain = true;
@@ -1226,16 +1278,19 @@ nsHostResolver::AssertOnQ(nsHostRecord *
     MOZ_ASSERT(false, "Did not find element");
 #endif
 }
 
 nsresult
 nsHostResolver::NativeLookup(nsHostRecord *aRec)
 {
     mLock.AssertCurrentThreadOwns();
+    if (aRec->type != nsIDNSService::RESOLVE_TYPE_DEFAULT) {
+        return NS_ERROR_UNKNOWN_HOST;
+    }
     RefPtr<nsHostRecord> rec(aRec);
 
     rec->mNativeStart = TimeStamp::Now();
 
     // Add rec to one of the pending queues, possibly removing it from mEvictionQ.
     if (rec->isInList()) {
         MOZ_ASSERT(mEvictionQSize);
         AssertOnQ(rec, mEvictionQ);
@@ -1296,34 +1351,40 @@ nsHostResolver::NameLookup(nsHostRecord 
 
     ResolverMode mode = rec->mResolverMode = Mode();
 
     rec->mNativeUsed = false;
     rec->mTRRUsed = false;
     rec->mNativeSuccess = false;
     rec->mTRRSuccess = 0;
     rec->mDidCallbacks = false;
-    rec->mTrrAUsed = nsHostRecord::INIT;
-    rec->mTrrAAAAUsed = nsHostRecord::INIT;
+
+    if (rec->type == nsIDNSService::RESOLVE_TYPE_DEFAULT) {
+        rec->mTrrAUsed = nsHostRecord::INIT;
+        rec->mTrrAAAAUsed = nsHostRecord::INIT;
+    }
 
     if (rec->flags & RES_DISABLE_TRR) {
         if (mode == MODE_TRRONLY) {
             return rv;
         }
         mode = MODE_NATIVEONLY;
     }
 
     if (!TRR_DISABLED(mode)) {
         rv = TrrLookup(rec);
     }
 
     if ((mode == MODE_PARALLEL) ||
         TRR_DISABLED(mode) ||
         (mode == MODE_SHADOW) ||
         ((mode == MODE_TRRFIRST) && NS_FAILED(rv))) {
+        if (rec->type != nsIDNSService::RESOLVE_TYPE_DEFAULT) {
+          return rv;
+        }
         rv = NativeLookup(rec);
     }
 
     return rv;
 }
 
 nsresult
 nsHostResolver::ConditionallyRefreshRecord(nsHostRecord *rec, const nsACString &host)
@@ -1518,16 +1579,42 @@ different_rrset(AddrInfo *rrset1, AddrIn
             LOG(("different_rrset true due to content change\n"));
             return true;
         }
     }
     LOG(("different_rrset false\n"));
     return false;
 }
 
+void
+nsHostResolver::AddToEvictionQ(nsHostRecord* rec)
+{
+    MOZ_ASSERT(!rec->isInList());
+    mEvictionQ.insertBack(rec);
+    if (mEvictionQSize < mMaxCacheEntries) {
+        mEvictionQSize++;
+    } else {
+        // remove first element on mEvictionQ
+        RefPtr<nsHostRecord> head = mEvictionQ.popFirst();
+        mRecordDB.Remove(*static_cast<nsHostKey *>(head.get()));
+
+        if (!head->negative) {
+            // record the age of the entry upon eviction.
+            TimeDuration age = TimeStamp::NowLoRes() - head->mValidStart;
+            Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE,
+                                  static_cast<uint32_t>(age.ToSeconds() / 60));
+            if (head->CheckExpiration(TimeStamp::Now()) !=
+                nsHostRecord::EXP_EXPIRED) {
+                Telemetry::Accumulate(Telemetry::DNS_PREMATURE_EVICTION,
+                                      static_cast<uint32_t>(age.ToSeconds() / 60));
+            }
+        }
+    }
+}
+
 //
 // CompleteLookup() checks if the resolving should be redone and if so it
 // returns LOOKUP_RESOLVEAGAIN, but only if 'status' is not NS_ERROR_ABORT.
 // takes ownership of AddrInfo parameter
 nsHostResolver::LookupStatus
 nsHostResolver::CompleteLookup(nsHostRecord* rec, nsresult status, AddrInfo* aNewRRSet, bool pb)
 {
     MutexAutoLock lock(mLock);
@@ -1724,38 +1811,17 @@ nsHostResolver::CompleteLookup(nsHostRec
             c->OnResolveHostComplete(this, rec, status);
         }
         rec->mDidCallbacks = true;
     }
 
     if (!rec->mResolving && !mShutdown) {
         rec->ResolveComplete();
 
-        // add to mEvictionQ
-        MOZ_ASSERT(!rec->isInList());
-        mEvictionQ.insertBack(rec);
-        if (mEvictionQSize < mMaxCacheEntries) {
-            mEvictionQSize++;
-        } else {
-            // remove first element on mEvictionQ
-            RefPtr<nsHostRecord> head = mEvictionQ.popFirst();
-            mRecordDB.Remove(*static_cast<nsHostKey *>(head.get()));
-
-            if (!head->negative) {
-                // record the age of the entry upon eviction.
-                TimeDuration age = TimeStamp::NowLoRes() - head->mValidStart;
-                Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE,
-                                      static_cast<uint32_t>(age.ToSeconds() / 60));
-                if (head->CheckExpiration(TimeStamp::Now()) !=
-                    nsHostRecord::EXP_EXPIRED) {
-                  Telemetry::Accumulate(Telemetry::DNS_PREMATURE_EVICTION,
-                                        static_cast<uint32_t>(age.ToSeconds() / 60));
-                }
-            }
-        }
+        AddToEvictionQ(rec);
     }
 
 #ifdef DNSQUERY_AVAILABLE
     // Unless the result is from TRR, resolve again to get TTL
     bool fromTRR = false;
     {
         MutexAutoLock lock(rec->addr_info_lock);
         if(rec->addr_info && rec->addr_info->IsTRR()) {
@@ -1772,33 +1838,75 @@ nsHostResolver::CompleteLookup(nsHostRec
         NS_WARNING_ASSERTION(
             NS_SUCCEEDED(rv),
             "Could not issue second async lookup for TTL.");
     }
 #endif
     return LOOKUP_OK;
 }
 
+nsHostResolver::LookupStatus
+nsHostResolver::CompleteLookupByType(nsHostRecord* rec, nsresult status,
+                                     const nsTArray<nsCString> *aResult,
+                                     uint32_t aTtl, bool pb)
+{
+    MutexAutoLock lock(mLock);
+    MOZ_ASSERT(rec);
+    MOZ_ASSERT(rec->pb == pb);
+
+    MOZ_ASSERT(rec->mResolving);
+    rec->mResolving--;
+
+    MutexAutoLock trrlock(rec->mTrrLock);
+    rec->mTrrTxt = nullptr;
+
+    if (NS_FAILED(status)) {
+        rec->SetExpiration(TimeStamp::NowLoRes(),
+                           NEGATIVE_RECORD_LIFETIME, 0);
+        MOZ_ASSERT(!aResult);
+        status = NS_ERROR_UNKNOWN_HOST;
+        rec->negative = true;
+    } else {
+        MOZ_ASSERT(aResult);
+        MutexAutoLock byTypeLock(rec->mRequestByTypeResultLock);
+        rec->mRequestByTypeResult = *aResult;
+        rec->SetExpiration(TimeStamp::NowLoRes(), aTtl, mDefaultGracePeriod);
+        rec->negative = false;
+    }
+
+    mozilla::LinkedList<RefPtr<nsResolveHostCallback>> cbs = std::move(rec->mCallbacks);
+
+    LOG(("nsHostResolver record %p calling back dns users\n", rec));
+
+    for (nsResolveHostCallback* c = cbs.getFirst(); c; c = c->removeAndGetNext()) {
+        c->OnResolveHostComplete(this, rec, status);
+    }
+
+    AddToEvictionQ(rec);
+    return LOOKUP_OK;
+}
+
 void
 nsHostResolver::CancelAsyncRequest(const nsACString &host,
+                                   uint16_t                aType,
                                    const OriginAttributes &aOriginAttributes,
                                    uint16_t                flags,
                                    uint16_t                af,
                                    nsIDNSListener         *aListener,
                                    nsresult                status)
 
 {
     MutexAutoLock lock(mLock);
 
     nsAutoCString originSuffix;
     aOriginAttributes.CreateSuffix(originSuffix);
 
     // Lookup the host record associated with host, flags & address family
 
-    nsHostKey key(host, flags, af,
+    nsHostKey key(host, aType, flags, af,
                   (aOriginAttributes.mPrivateBrowsingId > 0),
                   originSuffix);
     RefPtr<nsHostRecord> rec = mRecordDB.Get(key);
     if (rec) {
         nsHostRecord* recPtr = nullptr;
 
         for (RefPtr<nsResolveHostCallback> c : rec->mCallbacks) {
             if (c->EqualsAsyncListener(aListener)) {
--- a/netwerk/dns/nsHostResolver.h
+++ b/netwerk/dns/nsHostResolver.h
@@ -48,21 +48,22 @@ extern mozilla::Atomic<bool, mozilla::Re
 #define MAX_NON_PRIORITY_REQUESTS 150
 
 #define MAX_RESOLVER_THREADS (MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY + \
                               MAX_RESOLVER_THREADS_FOR_HIGH_PRIORITY)
 
 struct nsHostKey
 {
     const nsCString host;
+    uint16_t type;
     uint16_t flags;
     uint16_t af;
     bool     pb;
     const nsCString originSuffix;
-    explicit nsHostKey(const nsACString& host, uint16_t flags,
+    explicit nsHostKey(const nsACString& host, uint16_t  type, uint16_t flags,
                        uint16_t af, bool pb, const nsACString& originSuffix);
     bool operator==(const nsHostKey& other) const;
     size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
     PLDHashNumber Hash() const;
 };
 
 /**
  * nsHostRecord - ref counted object type stored in host resolver cache.
@@ -156,16 +157,19 @@ public:
     bool RemoveOrRefresh(); // Mark records currently being resolved as needed
                             // to resolve again.
     bool IsTRR() { return mTRRUsed; }
     void ResolveComplete();
     void Cancel();
 
     mozilla::net::ResolverMode mResolverMode;
 
+    nsTArray<nsCString> mRequestByTypeResult;
+    Mutex mRequestByTypeResultLock;
+
 private:
     friend class nsHostResolver;
 
     explicit nsHostRecord(const nsHostKey& key);
     mozilla::LinkedList<RefPtr<nsResolveHostCallback>> mCallbacks;
     nsAutoPtr<mozilla::net::AddrInfo> mFirstTRR; // partial TRR storage
     nsresult mFirstTRRresult;
 
@@ -190,16 +194,17 @@ private:
 
     enum {
         INIT, STARTED, OK, FAILED
     } mTrrAUsed, mTrrAAAAUsed;
 
     Mutex mTrrLock; // lock when accessing the mTrrA[AAA] pointers
     RefPtr<mozilla::net::TRR> mTrrA;
     RefPtr<mozilla::net::TRR> mTrrAAAA;
+    RefPtr<mozilla::net::TRR> mTrrTxt;
 
     // The number of times ReportUnusable() has been called in the record's
     // lifetime.
     uint32_t mBlacklistedCount;
 
     // a list of addresses associated with this record that have been reported
     // as unusable. the list is kept as a set of strings to make it independent
     // of gencnt.
@@ -266,16 +271,19 @@ public:
     NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
 
      enum LookupStatus {
         LOOKUP_OK,
         LOOKUP_RESOLVEAGAIN,
     };
 
     virtual LookupStatus CompleteLookup(nsHostRecord *, nsresult, mozilla::net::AddrInfo *, bool pb) = 0;
+    virtual LookupStatus CompleteLookupByType(nsHostRecord *, nsresult,
+                                              const nsTArray<nsCString> *aResult,
+                                              uint32_t aTtl, bool pb) = 0;
     virtual nsresult GetHostRecord(const nsACString &host,
                                    uint16_t flags, uint16_t af, bool pb,
                                    const nsCString &originSuffix,
                                    nsHostRecord **result)
     {
         return NS_ERROR_FAILURE;
     }
     virtual nsresult TrrLookup_unlocked(nsHostRecord *, mozilla::net::TRR *pushedTRR = nullptr)
@@ -319,42 +327,45 @@ public:
     /**
      * resolve the given hostname and originAttributes asynchronously.  the caller
      * can synthesize a synchronous host lookup using a lock and a cvar.  as noted
      * above the callback will occur re-entrantly from an unspecified thread.  the
      * host lookup cannot be canceled (cancelation can be layered above this by
      * having the callback implementation return without doing anything).
      */
     nsresult ResolveHost(const nsACString &hostname,
+                         uint16_t                         type,
                          const mozilla::OriginAttributes &aOriginAttributes,
                          uint16_t                         flags,
                          uint16_t                         af,
                          nsResolveHostCallback           *callback);
 
     /**
      * 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,
+                        uint16_t                         type,
                         const mozilla::OriginAttributes &aOriginAttributes,
                         uint16_t                         flags,
                         uint16_t                         af,
                         nsResolveHostCallback           *callback,
                         nsresult                         status);
 
     /**
      * Cancels an async request associated with the hostname, originAttributes, flags,
      * address family and listener.  Cancels first callback found which matches
      * these criteria.  These parameters should correspond to the parameters
      * passed to ResolveHost.  If this is the last callback associated with the
      * host record, it is removed from any request queues it might be on.
      */
     void CancelAsyncRequest(const nsACString &host,
+                            uint16_t                         type,
                             const mozilla::OriginAttributes &aOriginAttributes,
                             uint16_t                         flags,
                             uint16_t                         af,
                             nsIDNSListener                  *aListener,
                             nsresult                         status);
     /**
      * values for the flags parameter passed to ResolveHost and DetachCallback
      * that may be bitwise OR'd together.
@@ -379,16 +390,19 @@ public:
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
     /**
      * Flush the DNS cache.
      */
     void FlushCache();
 
     LookupStatus CompleteLookup(nsHostRecord *, nsresult, mozilla::net::AddrInfo *, bool pb) override;
+    LookupStatus CompleteLookupByType(nsHostRecord *, nsresult,
+                                      const nsTArray<nsCString> *aResult,
+                                      uint32_t aTtl, bool pb) override;
     nsresult GetHostRecord(const nsACString &host,
                            uint16_t flags, uint16_t af, bool pb,
                            const nsCString &originSuffix,
                            nsHostRecord **result) override;
     nsresult TrrLookup_unlocked(nsHostRecord *, mozilla::net::TRR *pushedTRR = nullptr) override;
 
 private:
    explicit nsHostResolver(uint32_t maxCacheEntries,
@@ -416,16 +430,18 @@ private:
     nsresult ConditionallyCreateThread(nsHostRecord *rec);
 
     /**
      * Starts a new lookup in the background for entries that are in the grace
      * period with a failed connect or all cached entries are negative.
      */
     nsresult ConditionallyRefreshRecord(nsHostRecord *rec, const nsACString &host);
 
+    void AddToEvictionQ(nsHostRecord* rec);
+
     void ThreadFunc();
 
     enum {
         METHOD_HIT = 1,
         METHOD_RENEWAL = 2,
         METHOD_NEGATIVE_HIT = 3,
         METHOD_LITERAL = 4,
         METHOD_OVERFLOW = 5,
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/nsIDNSByTypeRecord.idl
@@ -0,0 +1,22 @@
+/* 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 "nsISupports.idl"
+
+%{ C++
+#include "nsTArrayForwardDeclare.h"
+%}
+
+[ref] native CStringArrayRef(nsTArray<nsCString>);
+
+[scriptable, uuid(5d13241b-9d46-448a-90d8-77c418491026)]
+interface nsIDNSByTypeRecord : nsISupports
+{
+  CStringArrayRef getRecords();
+
+  /*
+   * Return concatenated strings.
+   */
+  ACString getRecordsAsOneString();
+};
--- a/netwerk/dns/nsIDNSListener.idl
+++ b/netwerk/dns/nsIDNSListener.idl
@@ -1,37 +1,52 @@
 /* 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 "nsISupports.idl"
 
 interface nsICancelable;
 interface nsIDNSRecord;
+interface nsIDNSByTypeRecord;
 
 /**
  * nsIDNSListener
  */
-[scriptable, function, uuid(27d49bfe-280c-49e0-bbaa-f6200c232c3d)]
+[scriptable, uuid(27d49bfe-280c-49e0-bbaa-f6200c232c3d)]
 interface nsIDNSListener : nsISupports
 {
     /**
      * called when an asynchronous host lookup completes.
      *
      * @param aRequest
      *        the value returned from asyncResolve.
      * @param aRecord
      *        the DNS record corresponding to the hostname that was resolved.
      *        this parameter is null if there was an error.
      * @param aStatus
      *        if the lookup failed, this parameter gives the reason.
      */
     void onLookupComplete(in nsICancelable aRequest,
                           in nsIDNSRecord  aRecord,
                           in nsresult      aStatus);
+    /**
+     * called when an asynchronous dns lookup by type completes.
+     *
+     * @param aRequest
+     *        the value returned from asyncResolve.
+     * @param aRecord
+     *        a string returned by the dns server.
+     * @param aStatus
+     *        if the lookup failed, this parameter gives the reason.
+     */
+    void onLookupByTypeComplete(in nsICancelable      aRequest,
+                                in nsIDNSByTypeRecord aResult,
+                                in nsresult           aStatus);
+
 };
 
 /**
  * nsIDNSListenerProxy:
  *
  * Must be implemented by classes that wrap the original listener passed to
  * nsIDNSService.AsyncResolve, so we have access to original listener for
  * comparison purposes.
--- a/netwerk/dns/nsIDNSService.idl
+++ b/netwerk/dns/nsIDNSService.idl
@@ -62,48 +62,125 @@ interface nsIDNSService : nsISupports
     nsresult asyncResolveNative(in AUTF8String       aHostName,
                                 in unsigned long     aFlags,
                                 in nsIDNSListener    aListener,
                                 in nsIEventTarget    aListenerTarget,
                                 in OriginAttributes  aOriginAttributes,
                                 out nsICancelable    aResult);
 
     /**
+      * kicks off an asynchronous host lookup by type, e.g. TXT.
+      *
+      * @param aHostName
+      *        the hostname or IP-address-literal to resolve.
+      * @param aType
+      *        one of RESOLVE_TYPE_*.
+      * @param aFlags
+      *        a bitwise OR of the RESOLVE_ prefixed constants defined below
+      *        except RESOLVE_CANONICAL_NAME, RESOLVE_DISABLE_IPV6 and
+      *        RESOLVE_DISABLE_IPV4.
+      * @param aListener
+      *        the listener to be notified when the result is available.
+      * @param aListenerTarget
+      *        optional parameter (may be null).  if non-null, this parameter
+      *        specifies the nsIEventTarget of the thread on which the
+      *        listener's onLookupComplete should be called.  however, if this
+      *        parameter is null, then onLookupComplete will be called on an
+      *        unspecified thread (possibly recursively).
+      * @param aOriginAttributes
+      *        the originAttribute for this resolving, the DNS cache will be
+      *        separated according to this originAttributes.
+      *
+      * @return An object that can be used to cancel the host lookup.
+      */
+    [implicit_jscontext, optional_argc]
+    nsICancelable asyncResolveByType(in AUTF8String       aHostName,
+                                     in unsigned short    aType,
+                                     in unsigned long     aFlags,
+                                     in nsIDNSListener    aListener,
+                                     in nsIEventTarget    aListenerTarget,
+                                     in jsval             aOriginAttributes);
+
+    [notxpcom]
+    nsresult asyncResolveByTypeNative(in AUTF8String       aHostName,
+                                      in unsigned short    aType,
+                                      in unsigned long     aFlags,
+                                      in nsIDNSListener    aListener,
+                                      in nsIEventTarget    aListenerTarget,
+                                      in OriginAttributes  aOriginAttributes,
+                                      out nsICancelable    aResult);
+
+    /**
      * Attempts to cancel a previously requested async DNS lookup
      *
      * @param aHostName
      *        the hostname or IP-address-literal to resolve.
      * @param aFlags
      *        a bitwise OR of the RESOLVE_ prefixed constants defined below.
      * @param aListener
      *        the original listener which was to be notified about the host lookup
      *        result - used to match request information to requestor.
      * @param aReason
      *        nsresult reason for the cancellation
      * @param aOriginAttributes
      *        the originAttribute for this resolving. This attribute is optional
      *        to avoid breaking add-ons.
-     *
-     * @return An object that can be used to cancel the host lookup.
      */
     [implicit_jscontext, optional_argc]
     void cancelAsyncResolve(in AUTF8String       aHostName,
                             in unsigned long     aFlags,
                             in nsIDNSListener    aListener,
                             in nsresult          aReason,
                  [optional] in jsval             aOriginAttributes);
 
     [notxpcom]
     nsresult cancelAsyncResolveNative(in AUTF8String       aHostName,
                                       in unsigned long     aFlags,
                                       in nsIDNSListener    aListener,
                                       in nsresult          aReason,
                                       in OriginAttributes  aOriginAttributes);
 
     /**
+     * Attempts to cancel a previously requested async DNS lookup
+     *
+     * @param aHostName
+     *        the hostname or IP-address-literal to resolve.
+     * @param  aType
+     *        one of RESOLVE_TYPE_*.
+     * @param aFlags
+     *        a bitwise OR of the RESOLVE_ prefixed constants defined below
+     *        except RESOLVE_CANONICAL_NAME, RESOLVE_DISABLE_IPV6 and
+     *        RESOLVE_DISABLE_IPV4.
+     * @param aListener
+     *        the original listener which was to be notified about the host lookup
+     *        result - used to match request information to requestor.
+     * @param aReason
+     *        nsresult reason for the cancellation
+     * @param aOriginAttributes
+     *        the originAttribute for this resolving. This attribute is optional
+     *        to avoid breaking add-ons.
+     *
+     */
+     [implicit_jscontext, optional_argc]
+     void cancelAsyncResolveByType(in AUTF8String       aHostName,
+                                   in unsigned short    aType,
+                                   in unsigned long     aFlags,
+                                   in nsIDNSListener    aListener,
+                                   in nsresult          aReason,
+                                   in jsval             aOriginAttributes);
+
+    [notxpcom]
+    nsresult cancelAsyncResolveByTypeNative(in AUTF8String       aHostName,
+                                            in unsigned short    aType,
+                                            in unsigned long     aFlags,
+                                            in nsIDNSListener    aListener,
+                                            in nsresult          aReason,
+                                            in OriginAttributes  aOriginAttributes);
+
+    /**
      * called to synchronously resolve a hostname.
      *
      * Since this method may block the calling thread for a long period of
      * time, it may not be accessed from the main thread.
      *
      * @param aHostName
      *        the hostname or IP-address-literal to resolve.
      * @param aFlags
@@ -194,9 +271,17 @@ interface nsIDNSService : nsISupports
      */
     const unsigned long RESOLVE_DISABLE_TRR = (1 << 9);
 
     /**
      * if set (together with RESOLVE_BYPASS_CACHE), invalidate the DNS
      * existing cache entry first (if existing) then make a new resolve.
      */
     const unsigned long RESOLVE_REFRESH_CACHE = (1 << 10);
+
+    /**
+     * This ure dns request types that are currently supported.
+     * RESOLVE_TYPE_DEFAULT is standard A/AAAA lookup
+     * The others (currently only TXT supported) are wireformat types
+     */
+    const unsigned long RESOLVE_TYPE_DEFAULT = 0;
+    const unsigned long RESOLVE_TYPE_TXT = 16;
 };
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -691,31 +691,33 @@ NeckoParent::RecvSpeculativeConnect(cons
       speculator->SpeculativeConnect2(uri, principal, nullptr);
     }
 
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-NeckoParent::RecvHTMLDNSPrefetch(const nsString& hostname,
+NeckoParent::RecvHTMLDNSPrefetch(const nsString& hostname, const bool& isHttps,
                                  const OriginAttributes& aOriginAttributes,
                                  const uint16_t& flags)
 {
-  nsHTMLDNSPrefetch::Prefetch(hostname, aOriginAttributes, flags);
+  nsHTMLDNSPrefetch::Prefetch(hostname, isHttps, aOriginAttributes, flags);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 NeckoParent::RecvCancelHTMLDNSPrefetch(const nsString& hostname,
+                                       const bool& isHttps,
                                        const OriginAttributes& aOriginAttributes,
                                        const uint16_t& flags,
                                        const nsresult& reason)
 {
-  nsHTMLDNSPrefetch::CancelPrefetch(hostname, aOriginAttributes, flags, reason);
+  nsHTMLDNSPrefetch::CancelPrefetch(hostname, isHttps, aOriginAttributes,
+                                    flags, reason);
   return IPC_OK();
 }
 
 PChannelDiverterParent*
 NeckoParent::AllocPChannelDiverterParent(const ChannelDiverterArgs& channel)
 {
   return new ChannelDiverterParent();
 }
--- a/netwerk/ipc/NeckoParent.h
+++ b/netwerk/ipc/NeckoParent.h
@@ -158,19 +158,21 @@ protected:
                                                              const nsCString& hostName,
                                                              const OriginAttributes& aOriginAttributes,
                                                              const uint32_t& flags) override;
   virtual bool DeallocPDNSRequestParent(PDNSRequestParent*) override;
   virtual mozilla::ipc::IPCResult RecvSpeculativeConnect(const URIParams& aURI,
                                                          const Principal& aPrincipal,
                                                          const bool& aAnonymous) override;
   virtual mozilla::ipc::IPCResult RecvHTMLDNSPrefetch(const nsString& hostname,
+                                                      const bool& isHttps,
                                                       const OriginAttributes& aOriginAttributes,
                                                       const uint16_t& flags) override;
   virtual mozilla::ipc::IPCResult RecvCancelHTMLDNSPrefetch(const nsString& hostname,
+                                                            const bool& isHttps,
                                                             const OriginAttributes& aOriginAttributes,
                                                             const uint16_t& flags,
                                                             const nsresult& reason) override;
   virtual PWebSocketEventListenerParent*
     AllocPWebSocketEventListenerParent(const uint64_t& aInnerWindowID) override;
   virtual bool DeallocPWebSocketEventListenerParent(PWebSocketEventListenerParent*) override;
 
   virtual PDataChannelParent*
--- a/netwerk/ipc/PNecko.ipdl
+++ b/netwerk/ipc/PNecko.ipdl
@@ -89,19 +89,20 @@ parent:
   async PredPredict(OptionalURIParams targetURI, OptionalURIParams sourceURI,
                     uint32_t reason, OriginAttributes originAttributes,
                     bool hasVerifier);
   async PredLearn(URIParams targetURI, OptionalURIParams sourceURI,
                   uint32_t reason, OriginAttributes originAttributes);
   async PredReset();
 
   async SpeculativeConnect(URIParams uri, Principal principal, bool anonymous);
-  async HTMLDNSPrefetch(nsString hostname, OriginAttributes originAttributes,
-                        uint16_t flags);
-  async CancelHTMLDNSPrefetch(nsString hostname, OriginAttributes originAttributes,
+  async HTMLDNSPrefetch(nsString hostname, bool isHttps,
+                        OriginAttributes originAttributes, uint16_t flags);
+  async CancelHTMLDNSPrefetch(nsString hostname, bool isHttps,
+                              OriginAttributes originAttributes,
                               uint16_t flags, nsresult reason);
 
   /**
    * channelId is used to establish a connection between redirect channels in
    * the parent and the child when we're redirecting to a data: URI.
    */
   async PDataChannel(uint32_t channelId);
   async PSimpleChannel(uint32_t channelId);
--- a/netwerk/protocol/http/TunnelUtils.cpp
+++ b/netwerk/protocol/http/TunnelUtils.cpp
@@ -1533,16 +1533,23 @@ SocketTransportShim::Bind(NetAddr *aLoca
 }
 
 NS_IMETHODIMP
 SocketTransportShim::GetFirstRetryError(nsresult *aFirstRetryError)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
+NS_IMETHODIMP
+SocketTransportShim::GetEsniUsed(bool *aEsniUsed)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
 #define FWD_TS_PTR(fx, ts) NS_IMETHODIMP \
 SocketTransportShim::fx(ts *arg) { return mWrapped->fx(arg); }
 
 #define FWD_TS_ADDREF(fx, ts) NS_IMETHODIMP \
 SocketTransportShim::fx(ts **arg) { return mWrapped->fx(arg); }
 
 #define FWD_TS(fx, ts) NS_IMETHODIMP \
 SocketTransportShim::fx(ts arg) { return mWrapped->fx(arg); }
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -8773,16 +8773,24 @@ nsHttpChannel::OnLookupComplete(nsICance
         if (mTransaction) {
             mTransaction->SetDNSWasRefreshed();
         }
     }
 
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsHttpChannel::OnLookupByTypeComplete(nsICancelable      *aRequest,
+                                      nsIDNSByTypeRecord *aRes,
+                                      nsresult            aStatus)
+{
+    return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpChannel internal functions
 //-----------------------------------------------------------------------------
 
 // Creates an URI to the given location using current URI for base and charset
 nsresult
 nsHttpChannel::CreateNewURI(const char *loc, nsIURI **newURI)
 {
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -12,16 +12,21 @@
 #define LOG(args) LOG5(args)
 #undef LOG_ENABLED
 #define LOG_ENABLED() LOG5_ENABLED()
 
 #define TLS_EARLY_DATA_NOT_AVAILABLE 0
 #define TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED 1
 #define TLS_EARLY_DATA_AVAILABLE_AND_USED 2
 
+#define ESNI_SUCCESSFUL 0
+#define ESNI_FAILED 1
+#define NO_ESNI_SUCCESSFUL 2
+#define NO_ESNI_FAILED 3
+
 #include "ASpdySession.h"
 #include "mozilla/ChaosMode.h"
 #include "mozilla/Telemetry.h"
 #include "nsHttpConnection.h"
 #include "nsHttpHandler.h"
 #include "nsHttpRequestHead.h"
 #include "nsHttpResponseHead.h"
 #include "nsIClassOfService.h"
@@ -29,17 +34,19 @@
 #include "nsISocketTransport.h"
 #include "nsSocketTransportService2.h"
 #include "nsISSLSocketControl.h"
 #include "nsISupportsPriority.h"
 #include "nsPreloadedStream.h"
 #include "nsProxyRelease.h"
 #include "nsSocketTransport2.h"
 #include "nsStringStream.h"
+#include "pkix/pkixnss.h"
 #include "sslt.h"
+#include "NSSErrorsService.h"
 #include "TunnelUtils.h"
 #include "TCPFastOpenLayer.h"
 
 namespace mozilla {
 namespace net {
 
 //-----------------------------------------------------------------------------
 // nsHttpConnection <public>
@@ -407,20 +414,22 @@ nsHttpConnection::EnsureNPNComplete(nsre
         mNPNComplete = true;
         return true;
     }
 
     if (mNPNComplete) {
         return true;
     }
 
-    nsresult rv;
+    nsresult rv = NS_OK;
     nsCOMPtr<nsISupports> securityInfo;
     nsCOMPtr<nsISSLSocketControl> ssl;
     nsAutoCString negotiatedNPN;
+    // This is neede for telemetry
+    bool handshakeSucceeded = false;
 
     GetSecurityInfo(getter_AddRefs(securityInfo));
     if (!securityInfo) {
         goto npnComplete;
     }
 
     ssl = do_QueryInterface(securityInfo, &rv);
     if (NS_FAILED(rv))
@@ -515,34 +524,39 @@ nsHttpConnection::EnsureNPNComplete(nsre
         return false;
     }
 
     if (NS_SUCCEEDED(rv)) {
         LOG(("nsHttpConnection::EnsureNPNComplete %p [%s] negotiated to '%s'%s\n",
              this, mConnInfo->HashKey().get(), negotiatedNPN.get(),
              mTLSFilter ? " [Double Tunnel]" : ""));
 
+        handshakeSucceeded = true;
+
+        int16_t tlsVersion;
+        ssl->GetSSLVersionUsed(&tlsVersion);
+        mConnInfo->SetLessThanTls13((tlsVersion < nsISSLSocketControl::TLS_VERSION_1_3) &&
+                                    (tlsVersion != nsISSLSocketControl::SSL_VERSION_UNKNOWN));
+
         bool earlyDataAccepted = false;
         if (mWaitingFor0RTTResponse) {
             // Check if early data has been accepted.
-            rv = ssl->GetEarlyDataAccepted(&earlyDataAccepted);
+            nsresult rvEarlyData = ssl->GetEarlyDataAccepted(&earlyDataAccepted);
             LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - early data "
                  "that was sent during 0RTT %s been accepted [rv=%" PRIx32 "].",
                  this, earlyDataAccepted ? "has" : "has not", static_cast<uint32_t>(rv)));
 
-            if (NS_FAILED(rv) ||
+            if (NS_FAILED(rvEarlyData) ||
                 NS_FAILED(mTransaction->Finish0RTT(!earlyDataAccepted, negotiatedNPN != mEarlyNegotiatedALPN))) {
                 LOG(("nsHttpConection::EnsureNPNComplete [this=%p] closing transaction %p", this, mTransaction.get()));
                 mTransaction->Close(NS_ERROR_NET_RESET);
                 goto npnComplete;
             }
         }
 
-        int16_t tlsVersion;
-        ssl->GetSSLVersionUsed(&tlsVersion);
         // Send the 0RTT telemetry only for tls1.3
         if (tlsVersion > nsISSLSocketControl::TLS_VERSION_1_2) {
             Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_NEGOTIATED,
                 (!mEarlyDataNegotiated) ? TLS_EARLY_DATA_NOT_AVAILABLE
                     : ((mWaitingFor0RTTResponse) ? TLS_EARLY_DATA_AVAILABLE_AND_USED
                                                  : TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED));
             if (mWaitingFor0RTTResponse) {
                 Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_ACCEPTED,
@@ -628,16 +642,29 @@ npnComplete:
         LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] resetting Start0RTTSpdy", this));
         mUsingSpdyVersion = SpdyVersion::NONE;
         mTransaction = nullptr;
         mSpdySession = nullptr;
         // We have to reset this here, just in case we end up starting spdy again,
         // so it can actually do everything it needs to do.
         mDid0RTTSpdy = false;
     }
+
+    if (ssl) {
+        // Telemetry for tls failure rate with and without esni;
+        bool esni;
+        mSocketTransport->GetEsniUsed(&esni);
+        Telemetry::Accumulate(Telemetry::ESNI_NOESNI_TLS_SUCCESS_RATE,
+                              (esni) ? ((handshakeSucceeded) ? ESNI_SUCCESSFUL : ESNI_FAILED)
+                                     : ((handshakeSucceeded) ? NO_ESNI_SUCCESSFUL : NO_ESNI_FAILED));
+    }
+
+    if (rv == psm::GetXPCOMFromNSSError(mozilla::pkix::MOZILLA_PKIX_ERROR_MITM_DETECTED)) {
+        gSocketTransportService->SetNotTrustedMitmDetected();
+    }
     return true;
 }
 
 void
 nsHttpConnection::OnTunnelNudged(TLSFilterTransaction *trans)
 {
     MOZ_ASSERT(OnSocketThread(), "not on socket thread");
     LOG(("nsHttpConnection::OnTunnelNudged %p\n", this));
--- a/netwerk/protocol/http/nsHttpConnectionInfo.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.cpp
@@ -45,28 +45,30 @@ namespace net {
 nsHttpConnectionInfo::nsHttpConnectionInfo(const nsACString &originHost,
                                            int32_t originPort,
                                            const nsACString &npnToken,
                                            const nsACString &username,
                                            nsProxyInfo *proxyInfo,
                                            const OriginAttributes &originAttributes,
                                            bool endToEndSSL)
     : mRoutedPort(443)
+    , mLessThanTls13(false)
 {
     Init(originHost, originPort, npnToken, username, proxyInfo, originAttributes, endToEndSSL);
 }
 
 nsHttpConnectionInfo::nsHttpConnectionInfo(const nsACString &originHost,
                                            int32_t originPort,
                                            const nsACString &npnToken,
                                            const nsACString &username,
                                            nsProxyInfo *proxyInfo,
                                            const OriginAttributes &originAttributes,
                                            const nsACString &routedHost,
                                            int32_t routedPort)
+  : mLessThanTls13(false)
 {
     mEndToEndSSL = true; // so DefaultPort() works
     mRoutedPort = routedPort == -1 ? DefaultPort() : routedPort;
 
     if (!originHost.Equals(routedHost) || (originPort != routedPort)) {
         mRoutedHost = routedHost;
     }
     Init(originHost, originPort, npnToken, username, proxyInfo, originAttributes, true);
--- a/netwerk/protocol/http/nsHttpConnectionInfo.h
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.h
@@ -150,16 +150,22 @@ public:
     bool FirstHopSSL() const { return mEndToEndSSL || mUsingHttpsProxy; }
 
     // Returns true when CONNECT is used to tunnel through the proxy (e.g. https:// or ws://)
     bool UsingConnect() const { return mUsingConnect; }
 
     // Returns true when origin/proxy is an RFC1918 literal.
     bool HostIsLocalIPLiteral() const;
 
+    bool GetLessThanTls13() const { return mLessThanTls13; }
+    void SetLessThanTls13(bool aLessThanTls13)
+    {
+      mLessThanTls13 = aLessThanTls13;
+    }
+
 private:
     void Init(const nsACString &host,
               int32_t port,
               const nsACString &npnToken,
               const nsACString &username,
               nsProxyInfo* proxyInfo,
               const OriginAttributes &originAttributes,
               bool EndToEndSSL);
@@ -179,16 +185,20 @@ private:
     bool                   mUsingConnect;  // if will use CONNECT with http proxy
     nsCString              mNPNToken;
     OriginAttributes       mOriginAttributes;
 
     uint32_t               mTlsFlags;
     uint16_t               mTrrUsed : 1;
     uint16_t               mTrrDisabled : 1;
 
+    bool mLessThanTls13; // This will be set to true if we negotiate less than
+                         // tls1.3. If the tls version is till not know or it
+                         // is 1.3 or greater the value will be false.
+
 // for RefPtr
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHttpConnectionInfo, override)
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // nsHttpConnectionInfo_h__
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -4071,16 +4071,20 @@ nsHalfOpenSocket::SetupStreams(nsISocket
     }
 
     if (mCaps & NS_HTTP_LOAD_ANONYMOUS)
         tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
 
     if (ci->GetPrivate())
         tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE;
 
+    if (ci->GetLessThanTls13()) {
+        tmpFlags |= nsISocketTransport::DONT_TRY_ESNI;
+    }
+
     if ((mCaps & NS_HTTP_BE_CONSERVATIVE) || ci->GetBeConservative()) {
         LOG(("Setting Socket to BE_CONSERVATIVE"));
         tmpFlags |= nsISocketTransport::BE_CONSERVATIVE;
     }
 
     if (mEnt->PreferenceKnown()) {
         if (mEnt->mPreferIPv6) {
             tmpFlags |= nsISocketTransport::DISABLE_IPV4;
--- a/netwerk/protocol/websocket/WebSocketChannel.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannel.cpp
@@ -3145,16 +3145,24 @@ WebSocketChannel::OnLookupComplete(nsICa
   }
 
   LOG(("WebSocket OnLookupComplete: Proceeding to ConditionallyConnect\n"));
   nsWSAdmissionManager::ConditionallyConnect(this);
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+WebSocketChannel::OnLookupByTypeComplete(nsICancelable      *aRequest,
+                                         nsIDNSByTypeRecord *aRes,
+                                         nsresult            aStatus)
+{
+  return NS_OK;
+}
+
 // nsIProtocolProxyCallback
 NS_IMETHODIMP
 WebSocketChannel::OnProxyAvailable(nsICancelable *aRequest, nsIChannel *aChannel,
                                    nsIProxyInfo *pi, nsresult status)
 {
   if (mStopped) {
     LOG(("WebSocketChannel::OnProxyAvailable: [%p] Request Already Stopped\n", this));
     mCancelable = nullptr;
--- a/netwerk/socket/nsISSLSocketControl.idl
+++ b/netwerk/socket/nsISSLSocketControl.idl
@@ -156,10 +156,22 @@ interface nsISSLSocketControl : nsISuppo
     [infallible] readonly attribute boolean bypassAuthentication;
 
     /*
      * failedVerification is true if any enforced certificate checks have failed.
      * Connections that have not yet tried to verify, have verifications bypassed,
      * or are using acceptable exceptions will all return false.
      */
     [infallible] readonly attribute boolean failedVerification;
+
+    /*
+     * esniTxt is a string that consists of the concatenated _esni. TXT records.
+     * This is a base64 encoded ESNIKeys structure.
+     */
+    attribute ACString esniTxt;
+
+    /**
+     * If the server certificate is present, serverCertIsBuiltInRoot is true if
+     * the root certificate for the server certificate is built in.
+     */
+    readonly attribute boolean serverRootCertIsBuiltInRoot;
 };
 
--- a/netwerk/socket/nsSOCKSIOLayer.cpp
+++ b/netwerk/socket/nsSOCKSIOLayer.cpp
@@ -519,16 +519,24 @@ nsSOCKSSocketInfo::OnLookupComplete(nsIC
     mState = SOCKS_DNS_COMPLETE;
     if (mFD) {
       ConnectToProxy(mFD);
       ForgetFD();
     }
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsSOCKSSocketInfo::OnLookupByTypeComplete(nsICancelable *aRequest,
+                                          nsIDNSByTypeRecord *res,
+                                          nsresult aStatus)
+{
+    return NS_OK;
+}
+
 PRStatus
 nsSOCKSSocketInfo::ConnectToProxy(PRFileDesc *fd)
 {
     PRStatus status;
     nsresult rv;
 
     MOZ_ASSERT(mState == SOCKS_DNS_COMPLETE,
                "Must have DNS to make connection!");
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_esni_dns_fetch.js
@@ -0,0 +1,105 @@
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var prefs;
+var h2Port;
+var listen;
+
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+var mainThread = threadManager.currentThread;
+
+const defaultOriginAttributes = {};
+
+function run_test() {
+  var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+  h2Port = env.get("MOZHTTP2_PORT");
+  Assert.notEqual(h2Port, null);
+  Assert.notEqual(h2Port, "");
+
+  // Set to allow the cert presented by our H2 server
+  do_get_profile();
+  prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+  prefs.setBoolPref("network.security.esni.enabled", true);
+  prefs.setBoolPref("network.http.spdy.enabled", true);
+  prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+  // the TRR server is on 127.0.0.1
+  prefs.setCharPref("network.trr.bootstrapAddress", "127.0.0.1");
+
+  // use the h2 server as DOH provider
+  prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/esni-dns");
+  // make all native resolve calls "secretly" resolve localhost instead
+  prefs.setBoolPref("network.dns.native-is-localhost", true);
+
+  // 0 - off, 1 - race, 2 TRR first, 3 TRR only, 4 shadow
+  prefs.setIntPref("network.trr.mode", 2); // TRR first
+  prefs.setBoolPref("network.trr.wait-for-portal", false);
+  // don't confirm that TRR is working, just go!
+  prefs.setCharPref("network.trr.confirmationNS", "skip");
+
+  // The moz-http2 cert is for foo.example.com and is signed by CA.cert.der
+  // so add that cert to the trust list as a signing cert.  // the foo.example.com domain name.
+  let certdb = Cc["@mozilla.org/security/x509certdb;1"]
+      .getService(Ci.nsIX509CertDB);
+  addCertFromFile(certdb, "CA.cert.der", "CTu,u,u");
+  do_test_pending();
+
+
+  listen = dns.asyncResolveByType("_esni.example.com", dns.RESOLVE_TYPE_TXT, 0, listenerFine, mainThread, defaultOriginAttributes);
+}
+
+registerCleanupFunction(() => {
+  prefs.clearUserPref("network.security.esni.enabled");
+  prefs.clearUserPref("network.http.spdy.enabled");
+  prefs.clearUserPref("network.http.spdy.enabled.http2");
+  prefs.clearUserPref("network.dns.localDomains");
+  prefs.clearUserPref("network.dns.native-is-localhost");
+  prefs.clearUserPref("network.trr.mode");
+  prefs.clearUserPref("network.trr.uri");
+  prefs.clearUserPref("network.trr.credentials");
+  prefs.clearUserPref("network.trr.wait-for-portal");
+  prefs.clearUserPref("network.trr.allow-rfc1918");
+  prefs.clearUserPref("network.trr.useGET");
+  prefs.clearUserPref("network.trr.confirmationNS");
+  prefs.clearUserPref("network.trr.bootstrapAddress");
+  prefs.clearUserPref("network.trr.blacklist-duration");
+  prefs.clearUserPref("network.trr.request-timeout");
+
+});
+
+function readFile(file) {
+  let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
+                  .createInstance(Ci.nsIFileInputStream);
+  fstream.init(file, -1, 0, 0);
+  let data = NetUtil.readInputStreamToString(fstream, fstream.available());
+  fstream.close();
+  return data;
+}
+
+function addCertFromFile(certdb, filename, trustString) {
+  let certFile = do_get_file(filename, false);
+  let der = readFile(certFile);
+  certdb.addCert(der, trustString);
+}
+
+var test_answer="bXkgdm9pY2UgaXMgbXkgcGFzc3dvcmQ=";
+
+// check that we do lookup by type fine
+var listenerFine = {
+  onLookupByTypeComplete: function(inRequest, inRecord, inStatus) {
+    if (inRequest == listen) {
+      Assert.ok(!inStatus);
+      var answer = inRecord.getRecordsAsOneString();
+      Assert.equal(answer, test_answer);
+      do_test_finished();
+    }
+  },
+  QueryInterface: function(aIID) {
+    if (aIID.equals(Ci.nsIDNSListener) ||
+        aIID.equals(Ci.nsISupports)) {
+      return this;
+    }
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  }
+};
+
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -413,8 +413,12 @@ skip-if = os == "android"
 run-sequentially = node server exceptions dont replay well
 [test_trr.js]
 # http2-using tests require node available
 skip-if = os == "android"
 [test_ioservice.js]
 [test_substituting_protocol_handler.js]
 [test_captive_portal_service.js]
 skip-if = os == "android" # CP service is disabled on Android
+run-sequentially = node server exceptions dont replay well
+[test_esni_dns_fetch.js]
+# http2-using tests require node available
+skip-if = os == "android"
--- a/security/manager/ssl/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/nsNSSIOLayer.cpp
@@ -1002,16 +1002,72 @@ nsNSSSocketInfo::CloseSocketAndDestroy()
 
   popped->identity = PR_INVALID_IO_LAYER;
   NS_RELEASE_THIS();
   popped->dtor(popped);
 
   return PR_SUCCESS;
 }
 
+NS_IMETHODIMP
+nsNSSSocketInfo::GetEsniTxt(nsACString & aEsniTxt)
+{
+  aEsniTxt = mEsniTxt;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::SetEsniTxt(const nsACString & aEsniTxt)
+{
+  mEsniTxt = aEsniTxt;
+
+  if (mEsniTxt.Length()) {
+    fprintf(stderr,"\n\nTODO - SSL_EnableSNI() [%s] (%d bytes)\n",
+            mEsniTxt.get(), mEsniTxt.Length());
+
+#if 0
+    if (SECSuccess != SSL_EnableESNI(mFd,
+                                     reinterpret_cast<const PRUint8*>(mEsniTxt.get()),
+                                     mEsniTxt.Length(), "dummy.invalid")) {
+      return NS_ERROR_FAILURE;
+    }
+#endif
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::GetServerRootCertIsBuiltInRoot(bool *aIsBuiltInRoot)
+{
+  *aIsBuiltInRoot = false;
+
+  if (!HasServerCert()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsCOMPtr<nsIX509CertList> certList;
+  nsresult rv = GetSucceededCertChain(getter_AddRefs(certList));
+  if (NS_SUCCEEDED(rv)) {
+    if (!certList) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+    RefPtr<nsNSSCertList> nssCertList = certList->GetCertList();
+    nsCOMPtr<nsIX509Cert> cert;
+    rv = nssCertList->GetRootCertificate(cert);
+    if (NS_SUCCEEDED(rv)) {
+      if (!cert) {
+        return NS_ERROR_NOT_AVAILABLE;
+      }
+      rv = cert->GetIsBuiltInRoot(aIsBuiltInRoot);
+    }
+  }
+  return rv;
+}
+
 #if defined(DEBUG_SSL_VERBOSE) && defined(DUMP_BUFFER)
 // Dumps a (potentially binary) buffer using SSM_DEBUG.  (We could have used
 // the version in ssltrace.c, but that's specifically tailored to SSLTRACE.)
 #define DUMPBUF_LINESIZE 24
 static void
 nsDumpBuffer(unsigned char* buf, int len)
 {
   char hexbuf[DUMPBUF_LINESIZE*3+1];
--- a/security/manager/ssl/nsNSSIOLayer.h
+++ b/security/manager/ssl/nsNSSIOLayer.h
@@ -178,16 +178,17 @@ private:
   SSLVersionRange mTLSVersionRange;
   bool mHandshakePending;
   bool mRememberClientAuthCertificate;
   bool mPreliminaryHandshakeDone; // after false start items are complete
 
   nsresult ActivateSSL();
 
   nsCString mNegotiatedNPN;
+  nsCString mEsniTxt;
   bool      mNPNCompleted;
   bool      mEarlyDataAccepted;
   bool      mDenyClientCert;
   bool      mFalseStartCallbackCalled;
   bool      mFalseStarted;
   bool      mIsFullHandshake;
   bool      mHandshakeCompleted;
   bool      mJoined;
--- a/testing/xpcshell/moz-http2/moz-http2.js
+++ b/testing/xpcshell/moz-http2/moz-http2.js
@@ -753,16 +753,42 @@ function handleRequest(req, res) {
     res.setHeader('Content-Type', 'application/dns-message');
     res.setHeader('Content-Length', content.length);
     res.writeHead(200);
     res.write(content);
     res.end("");
     return;
   }
 
+  // for use with test_esni_dns_fetch.js
+  else if (u.pathname === "/esni-dns") {
+    content = new Buffer("0000" +
+                         "8180" +
+                         "0001" + // QDCOUNT
+                         "0001" + // ANCOUNT
+                         "00000000" + // NSCOUNT + ARCOUNT
+                         "055F65736E69076578616D706C6503636F6D00" + // esni.example.com
+                         "00100001" + // question type (TXT) + question class (IN)
+
+                         "C00C" + // name pointer to .example.com
+                         "0010" + // type (TXT)
+                         "0001" + // class
+                         "00000037" + // TTL
+                         "0021" + // RDLENGTH
+                         "2062586B67646D39705932556761584D6762586B676347467A63336476636D513D", // esni keys.
+                         "hex");
+
+    res.setHeader('Content-Type', 'application/dns-message');
+    res.setHeader('Content-Length', content.length);
+    res.writeHead(200);
+    res.write(content);
+    res.end("");
+    return;
+  }
+
   else if (u.pathname === "/.well-known/http-opportunistic") {
     res.setHeader('Cache-Control', 'no-cache');
     res.setHeader('Content-Type', 'application/json');
     res.writeHead(200, "OK");
     res.end('{"http://' + req.headers['host'] + '": { "tls-ports": [' + serverPort + '] }}');
     return;
   }
 
--- a/toolkit/components/extensions/parent/ext-dns.js
+++ b/toolkit/components/extensions/parent/ext-dns.js
@@ -28,38 +28,40 @@ this.dns = class extends ExtensionAPI {
         resolve: function(hostname, flags) {
           let dnsFlags = flags.reduce((mask, flag) => mask | dnssFlags[flag], 0);
 
           return new Promise((resolve, reject) => {
             let request;
             let response = {
               addresses: [],
             };
-            let listener = (inRequest, inRecord, inStatus) => {
-              if (inRequest === request) {
-                if (!Components.isSuccessCode(inStatus)) {
-                  return reject({message: getErrorString(inStatus)});
-                }
-                if (dnsFlags & Ci.nsIDNSService.RESOLVE_CANONICAL_NAME) {
-                  try {
-                    response.canonicalName = inRecord.canonicalName;
-                  } catch (e) {
-                    // no canonicalName
+            let listener = {
+              onLookupComplete: function(inRequest, inRecord, inStatus) {
+                if (inRequest === request) {
+                  if (!Components.isSuccessCode(inStatus)) {
+                    return reject({message: getErrorString(inStatus)});
                   }
+                  if (dnsFlags & Ci.nsIDNSService.RESOLVE_CANONICAL_NAME) {
+                    try {
+                      response.canonicalName = inRecord.canonicalName;
+                    } catch (e) {
+                      // no canonicalName
+                    }
+                  }
+                  response.isTRR = inRecord.IsTRR();
+                  while (inRecord.hasMore()) {
+                    let addr = inRecord.getNextAddrAsString();
+                    // Sometimes there are duplicate records with the same ip.
+                    if (!response.addresses.includes(addr)) {
+                      response.addresses.push(addr);
+                    }
+                  }
+                  return resolve(response);
                 }
-                response.isTRR = inRecord.IsTRR();
-                while (inRecord.hasMore()) {
-                  let addr = inRecord.getNextAddrAsString();
-                  // Sometimes there are duplicate records with the same ip.
-                  if (!response.addresses.includes(addr)) {
-                    response.addresses.push(addr);
-                  }
-                }
-                return resolve(response);
-              }
+              },
             };
             try {
               request = dnss.asyncResolve(hostname, dnsFlags, listener, null, {} /* defaultOriginAttributes */);
             } catch (e) {
               // handle exceptions such as offline mode.
               return reject({message: e.name});
             }
           });
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -2055,16 +2055,46 @@
     "expires_in_version": "66",
     "kind": "exponential",
     "high": 60000,
     "n_buckets": 100,
     "description": "Amount of bytes sent using TLS early data at the start of a TLS connection for a given channel.",
     "alert_emails": ["necko@mozilla.com", "ddamjanovic@mozilla.com"],
     "bug_numbers": [1296288]
   },
+  "ESNI_KEYS_RECORD_FETCH_DELAYS": {
+    "record_in_processes": ["main"],
+    "expires_in_version": "70",
+    "kind": "exponential",
+    "high": 60000,
+    "n_buckets": 100,
+    "description": "Added delays caused the esni keys fetching.(ms)",
+    "alert_emails": ["necko@mozilla.com", "ddamjanovic@mozilla.com"],
+    "bug_numbers": [1473736],
+    "releaseChannelCollection": "opt-out"
+  },
+  "ESNI_KEYS_RECORDS_FOUND": {
+    "record_in_processes": ["main"],
+    "expires_in_version": "70",
+    "kind": "boolean",
+    "description": "ESNI Keys found rate.",
+    "alert_emails": ["necko@mozilla.com", "ddamjanovic@mozilla.com"],
+    "bug_numbers": [1473736],
+    "releaseChannelCollection": "opt-out"
+  },
+  "ESNI_NOESNI_TLS_SUCCESS_RATE": {
+    "record_in_processes": ["main"],
+    "expires_in_version": "70",
+    "kind": "categorical",
+    "labels": ["EsniTLSSucceeded", "EsniTLSFailed", "NoEsniTLSSucceeded", "NoEsniTLSFailed"],
+    "description": "TLS handshake with and without esni success rate.",
+    "alert_emails": ["necko@mozilla.com", "ddamjanovic@mozilla.com"],
+    "bug_numbers": [1473736],
+    "releaseChannelCollection": "opt-out"
+  },
   "SSL_HANDSHAKE_VERSION": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["seceng-telemetry@mozilla.com"],
     "bug_numbers": [1250568,1340021],
     "releaseChannelCollection": "opt-out",
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 16,