Bug 967792 - Make localhost resolve offline. r=dragana
authorPatrick McManus <mcmanus@ducksong.com>
Wed, 25 Feb 2015 14:02:38 -0500
changeset 259615 c49df0e00b59688e16207c64ee35f500b78168b4
parent 259614 d1133a390c64e5a9c5fbe036306a5cad36230633
child 259616 9c40ffbcf6b32d688abf089f2cce3c85dd8bdb29
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdragana
bugs967792
milestone39.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 967792 - Make localhost resolve offline. r=dragana
browser/components/loop/test/xpcshell/test_loopservice_hawk_errors.js
dom/base/test/unit/test_error_codes.js
modules/libpref/init/all.js
netwerk/dns/nsDNSService2.cpp
netwerk/dns/nsDNSService2.h
netwerk/test/unit/test_dns_offline.js
netwerk/test/unit/xpcshell.ini
services/sync/tests/unit/test_errorhandler_sync_checkServerError.js
--- a/browser/components/loop/test/xpcshell/test_loopservice_hawk_errors.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_hawk_errors.js
@@ -32,16 +32,17 @@ add_task(function* setup_server() {
   loopServer.registerPathHandler("/401", errorRequestHandler);
   loopServer.registerPathHandler("/404", errorRequestHandler);
   loopServer.registerPathHandler("/500", errorRequestHandler);
   loopServer.registerPathHandler("/503", errorRequestHandler);
 });
 
 add_task(function* error_offline() {
   Services.io.offline = true;
+  Services.prefs.setBoolPref("network.dns.offline-localhost", false);
   yield MozLoopServiceInternal.hawkRequestInternal(LOOP_SESSION_TYPE.GUEST, "/offline", "GET").then(
     () => Assert.ok(false, "Should have rejected"),
     (error) => {
       MozLoopServiceInternal.setError("testing", error);
       Assert.strictEqual(MozLoopService.errors.size, 1, "Should be one error");
 
       // Network errors are converted to the "network" errorType.
       let err = MozLoopService.errors.get("network");
@@ -174,16 +175,17 @@ function run_test() {
 
   // Set the expiry time one hour in the future so that an error is shown when the guest session expires.
   MozLoopServiceInternal.expiryTimeSeconds = (Date.now() / 1000) + 3600;
 
   do_register_cleanup(() => {
     Services.prefs.clearUserPref("loop.hawk-session-token");
     Services.prefs.clearUserPref("loop.hawk-session-token.fxa");
     Services.prefs.clearUserPref("loop.urlsExpiryTimeSeconds");
+    Services.prefs.clearUserPref("network.dns.offline-localhost");
     MozLoopService.errors.clear();
   });
 
   run_next_test();
 }
 
 function* cleanup_between_tests() {
   MozLoopService.errors.clear();
--- a/dom/base/test/unit/test_error_codes.js
+++ b/dom/base/test/unit/test_error_codes.js
@@ -1,16 +1,19 @@
 /* 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/.
  */
 
 var gExpectedStatus = null;
 var gNextTestFunc   = null;
 
+var prefs = Components.classes["@mozilla.org/preferences-service;1"].
+    getService(Components.interfaces.nsIPrefBranch);
+
 var asyncXHR = {
   load: function() {
     var request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                             .createInstance(Components.interfaces.nsIXMLHttpRequest);
     request.open("GET", "http://localhost:4444/test_error_code.xml", true);
 
     var self = this;
     request.addEventListener("error", function(event) { self.onError(event); }, false);
@@ -34,28 +37,30 @@ function run_test_pt1() {
                             .getService(Components.interfaces.nsIIOService);
 
   try {
     ioService.manageOfflineStatus = false;
   }
   catch (e) {
   }
   ioService.offline = true;
+  prefs.setBoolPref("network.dns.offline-localhost", false);
 
   gExpectedStatus = Components.results.NS_ERROR_OFFLINE;
   gNextTestFunc = run_test_pt2;
   dump("Testing error returned by async XHR when the network is offline\n");
   asyncXHR.load();
 }
 
 // connection refused
 function run_test_pt2() {
   var ioService = Components.classes["@mozilla.org/network/io-service;1"]
                             .getService(Components.interfaces.nsIIOService);
   ioService.offline = false;
+  prefs.clearUserPref("network.dns.offline-localhost");
 
   gExpectedStatus = Components.results.NS_ERROR_CONNECTION_REFUSED;
   gNextTestFunc = end_test;
   dump("Testing error returned by aync XHR when the connection is refused\n");
   asyncXHR.load();
 }
 
 function end_test() {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1584,16 +1584,19 @@ pref("network.dns.get-ttl", true);
 
 // The grace period allows the DNS cache to use expired entries, while kicking off
 // a revalidation in the background.
 pref("network.dnsCacheExpirationGracePeriod", 60);
 
 // This preference can be used to turn off DNS prefetch.
 pref("network.dns.disablePrefetch", false);
 
+// Contols whether or not "localhost" should resolve when offline
+pref("network.dns.offline-localhost", true);
+
 // This preference controls whether or not URLs with UTF-8 characters are
 // escaped.  Set this preference to TRUE for strict RFC2396 conformance.
 pref("network.standard-url.escape-utf8", true);
 
 // This preference controls whether or not URLs are always encoded and sent as
 // UTF-8.
 pref("network.standard-url.encode-utf8", true);
 
--- a/netwerk/dns/nsDNSService2.cpp
+++ b/netwerk/dns/nsDNSService2.cpp
@@ -45,16 +45,17 @@ using namespace mozilla::net;
 
 static const char kPrefDnsCacheEntries[]     = "network.dnsCacheEntries";
 static const char kPrefDnsCacheExpiration[]  = "network.dnsCacheExpiration";
 static const char kPrefDnsCacheGrace[]       = "network.dnsCacheExpirationGracePeriod";
 static const char kPrefIPv4OnlyDomains[]     = "network.dns.ipv4OnlyDomains";
 static const char kPrefDisableIPv6[]         = "network.dns.disableIPv6";
 static const char kPrefDisablePrefetch[]     = "network.dns.disablePrefetch";
 static const char kPrefDnsLocalDomains[]     = "network.dns.localDomains";
+static const char kPrefDnsOfflineLocalhost[] = "network.dns.offline-localhost";
 static const char kPrefDnsNotifyResolution[] = "network.dns.notifyResolution";
 
 //-----------------------------------------------------------------------------
 
 class nsDNSRecord : public nsIDNSRecord
 {
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
@@ -530,22 +531,22 @@ nsDNSService::GetSingleton()
 }
 
 NS_IMETHODIMP
 nsDNSService::Init()
 {
     if (mResolver)
         return NS_OK;
     NS_ENSURE_TRUE(!mResolver, NS_ERROR_ALREADY_INITIALIZED);
-
     // prefs
     uint32_t maxCacheEntries  = 400;
     uint32_t defaultCacheLifetime = 120; // seconds
     uint32_t defaultGracePeriod = 60; // seconds
     bool     disableIPv6      = false;
+    bool     offlineLocalhost = true;
     bool     disablePrefetch  = false;
     int      proxyType        = nsIProtocolProxyService::PROXYCONFIG_DIRECT;
     bool     notifyResolution = false;
 
     nsAdoptingCString ipv4OnlyDomains;
     nsAdoptingCString localDomains;
 
     // read prefs
@@ -558,16 +559,17 @@ nsDNSService::Init()
             defaultCacheLifetime = val;
         if (NS_SUCCEEDED(prefs->GetIntPref(kPrefDnsCacheGrace, &val)))
             defaultGracePeriod = val;
 
         // ASSUMPTION: pref branch does not modify out params on failure
         prefs->GetBoolPref(kPrefDisableIPv6, &disableIPv6);
         prefs->GetCharPref(kPrefIPv4OnlyDomains, getter_Copies(ipv4OnlyDomains));
         prefs->GetCharPref(kPrefDnsLocalDomains, getter_Copies(localDomains));
+        prefs->GetBoolPref(kPrefDnsOfflineLocalhost, &offlineLocalhost);
         prefs->GetBoolPref(kPrefDisablePrefetch, &disablePrefetch);
 
         // If a manual proxy is in use, disable prefetch implicitly
         prefs->GetIntPref("network.proxy.type", &proxyType);
         prefs->GetBoolPref(kPrefDnsNotifyResolution, &notifyResolution);
     }
 
     if (mFirstTime) {
@@ -576,16 +578,17 @@ nsDNSService::Init()
         // register as prefs observer
         if (prefs) {
             prefs->AddObserver(kPrefDnsCacheEntries, this, false);
             prefs->AddObserver(kPrefDnsCacheExpiration, this, false);
             prefs->AddObserver(kPrefDnsCacheGrace, this, false);
             prefs->AddObserver(kPrefIPv4OnlyDomains, this, false);
             prefs->AddObserver(kPrefDnsLocalDomains, this, false);
             prefs->AddObserver(kPrefDisableIPv6, this, false);
+            prefs->AddObserver(kPrefDnsOfflineLocalhost, this, false);
             prefs->AddObserver(kPrefDisablePrefetch, this, false);
             prefs->AddObserver(kPrefDnsNotifyResolution, this, false);
 
             // Monitor these to see if there is a change in proxy configuration
             // If a manual proxy is in use, disable prefetch implicitly
             prefs->AddObserver("network.proxy.type", this, false);
         }
 
@@ -616,16 +619,17 @@ nsDNSService::Init()
                                          defaultGracePeriod,
                                          getter_AddRefs(res));
     if (NS_SUCCEEDED(rv)) {
         // now, set all of our member variables while holding the lock
         MutexAutoLock lock(mLock);
         mResolver = res;
         mIDN = idn;
         mIPv4OnlyDomains = ipv4OnlyDomains; // exchanges buffer ownership
+        mOfflineLocalhost = offlineLocalhost;
         mDisableIPv6 = disableIPv6;
 
         // Disable prefetching either by explicit preference or if a manual proxy is configured 
         mDisablePrefetch = disablePrefetch || (proxyType == nsIProtocolProxyService::PROXYCONFIG_MANUAL);
 
         mLocalDomains.Clear();
         if (localDomains) {
             nsCCharSeparatedTokenizer tokenizer(localDomains, ',',
@@ -748,23 +752,25 @@ nsDNSService::AsyncResolveExtended(const
     if (mNotifyResolution) {
         NS_DispatchToMainThread(new NotifyDNSResolution(mObserverService,
                                                         aHostname));
     }
 
     if (!res)
         return NS_ERROR_OFFLINE;
 
-    if (mOffline)
-        flags |= RESOLVE_OFFLINE;
-
     nsCString hostname;
     if (!PreprocessHostname(localDomain, aHostname, idn, hostname))
         return NS_ERROR_FAILURE;
 
+    if (mOffline &&
+        (!mOfflineLocalhost || !hostname.LowerCaseEqualsASCII("localhost"))) {
+        flags |= RESOLVE_OFFLINE;
+    }
+
     // make sure JS callers get notification on the main thread
     nsCOMPtr<nsIXPConnectWrappedJS> wrappedListener = do_QueryInterface(listener);
     if (wrappedListener && !target) {
         nsCOMPtr<nsIThread> mainThread;
         NS_GetMainThread(getter_AddRefs(mainThread));
         target = do_QueryInterface(mainThread);
     }
 
@@ -862,23 +868,25 @@ nsDNSService::Resolve(const nsACString &
 
     if (mNotifyResolution) {
         NS_DispatchToMainThread(new NotifyDNSResolution(mObserverService,
                                                         aHostname));
     }
 
     NS_ENSURE_TRUE(res, NS_ERROR_OFFLINE);
 
-    if (mOffline)
-        flags |= RESOLVE_OFFLINE;
-
     nsCString hostname;
     if (!PreprocessHostname(localDomain, aHostname, idn, hostname))
         return NS_ERROR_FAILURE;
 
+    if (mOffline &&
+        (!mOfflineLocalhost || !hostname.LowerCaseEqualsASCII("localhost"))) {
+        flags |= RESOLVE_OFFLINE;
+    }
+
     //
     // sync resolve: since the host resolver only works asynchronously, we need
     // to use a mutex and a condvar to wait for the result.  however, since the
     // result may be in the resolvers cache, we might get called back recursively
     // on the same thread.  so, our mutex needs to be re-entrant.  in other words,
     // we need to use a monitor! ;-)
     //
     
--- a/netwerk/dns/nsDNSService2.h
+++ b/netwerk/dns/nsDNSService2.h
@@ -55,13 +55,14 @@ private:
     // IPv4 DNS lookups are performed. This allows the user to disable IPv6 on
     // a per-domain basis and work around broken DNS servers. See bug 68796.
     nsAdoptingCString                         mIPv4OnlyDomains;
     bool                                      mDisableIPv6;
     bool                                      mDisablePrefetch;
     bool                                      mFirstTime;
     bool                                      mOffline;
     bool                                      mNotifyResolution;
+    bool                                      mOfflineLocalhost;
     nsMainThreadPtrHandle<nsIObserverService> mObserverService;
     nsTHashtable<nsCStringHashKey>            mLocalDomains;
 };
 
 #endif //nsDNSService2_h__
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_dns_offline.js
@@ -0,0 +1,74 @@
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+var mainThread = threadManager.currentThread;
+
+var listener1 = {
+  onLookupComplete: function(inRequest, inRecord, inStatus) {
+    do_check_eq(inStatus, Cr.NS_ERROR_OFFLINE);
+    test2();
+    do_test_finished();
+  }
+};
+
+var listener2 = {
+  onLookupComplete: function(inRequest, inRecord, inStatus) {
+    do_check_eq(inStatus, Cr.NS_OK);
+    var answer = inRecord.getNextAddrAsString();
+    do_check_true(answer == "127.0.0.1" || answer == "::1");
+    test3();
+    do_test_finished();
+  }
+};
+
+var listener3 = {
+  onLookupComplete: function(inRequest, inRecord, inStatus) {
+    do_check_eq(inStatus, Cr.NS_OK);
+    var answer = inRecord.getNextAddrAsString();
+    do_check_true(answer == "127.0.0.1" || answer == "::1");
+    cleanup();
+    do_test_finished();
+  }
+};
+
+function run_test() {
+  do_test_pending();
+  prefs.setBoolPref("network.dns.offline-localhost", false);
+  ioService.offline = true;
+  try {
+    dns.asyncResolve("localhost", 0, listener1, mainThread);
+  } catch (e) {
+      do_check_eq(e.result, Cr.NS_ERROR_OFFLINE);
+      test2();
+      do_test_finished();
+  }
+}
+
+function test2() {
+  do_test_pending();
+  prefs.setBoolPref("network.dns.offline-localhost", true);
+  ioService.offline = false;
+  ioService.offline = true;
+  // we need to let the main thread run and apply the changes
+  do_timeout(0, test2Continued);
+}
+
+function test2Continued() {
+  dns.asyncResolve("localhost", 0, listener2, mainThread);
+}
+
+function test3() {
+  do_test_pending();
+  ioService.offline = false;
+  // we need to let the main thread run and apply the changes
+  do_timeout(0, test3Continued);
+}
+
+function test3Continued() {
+  dns.asyncResolve("localhost", 0, listener3, mainThread);
+}
+
+function cleanup() {
+  prefs.clearUserPref("network.dns.offline-localhost");
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -171,16 +171,17 @@ skip-if = bits != 32
 [test_content_sniffer.js]
 [test_cookie_header.js]
 [test_cookiejars.js]
 [test_cookiejars_safebrowsing.js]
 [test_dns_cancel.js]
 [test_dns_per_interface.js]
 [test_data_protocol.js]
 [test_dns_service.js]
+[test_dns_offline.js]
 [test_dns_localredirect.js]
 [test_dns_proxy_bypass.js]
 [test_duplicate_headers.js]
 [test_chunked_responses.js]
 [test_content_length_underrun.js]
 [test_event_sink.js]
 [test_extract_charset_from_content_type.js]
 [test_fallback_no-cache-entry_canceled.js]
--- a/services/sync/tests/unit/test_errorhandler_sync_checkServerError.js
+++ b/services/sync/tests/unit/test_errorhandler_sync_checkServerError.js
@@ -195,30 +195,32 @@ add_identity_test(this, function test_se
 
 add_identity_test(this, function test_service_offline() {
   _("Test: Wanting to sync in offline mode leads to the right status code but does not increment the ignorable error count.");
   let server = sync_httpd_setup();
   yield setUp(server);
   let deferred = Promise.defer();
   server.stop(() => {
     Services.io.offline = true;
+    Services.prefs.setBoolPref("network.dns.offline-localhost", false);
 
     try {
       do_check_eq(Status.sync, SYNC_SUCCEEDED);
 
       Service._loggedIn = true;
       Service.sync();
 
       do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
       do_check_eq(Status.service, SYNC_FAILED);
     } finally {
       Status.resetSync();
       Service.startOver();
     }
     Services.io.offline = false;
+    Services.prefs.clearUserPref("network.dns.offline-localhost");
     deferred.resolve();
   });
   yield deferred.promise;
 });
 
 add_identity_test(this, function test_engine_networkError() {
   _("Test: Network related exceptions from engine.sync() lead to the right status code.");
   let server = sync_httpd_setup();