Bug 87717 - Allow offline mode to connect to loopback r=mcmanus sr=cbiesinger
authorAdam Dane [:hobophobe] <unusualtears@gmail.com>
Mon, 17 Sep 2012 18:45:10 -0500
changeset 107434 c122628565e57ace2888ac42ad0e326f7d5f6f2c
parent 107433 15f6aaf8ed468d90797042a129b0eb27284680de
child 107435 7e4a6bba7ce557aee006d24974d4ba721b47c1cd
push id23486
push usergraememcc_firefox@graeme-online.co.uk
push dateWed, 19 Sep 2012 14:18:40 +0000
treeherdermozilla-central@0c8ac138706e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus, cbiesinger
bugs87717
milestone18.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 87717 - Allow offline mode to connect to loopback r=mcmanus sr=cbiesinger
browser/base/content/test/browser_bug435325.js
content/base/test/unit/test_error_codes.js
netwerk/base/public/nsASocketHandler.h
netwerk/base/public/nsPISocketTransportService.idl
netwerk/base/src/nsIOService.cpp
netwerk/base/src/nsServerSocket.cpp
netwerk/base/src/nsServerSocket.h
netwerk/base/src/nsSocketTransport2.cpp
netwerk/base/src/nsSocketTransport2.h
netwerk/base/src/nsSocketTransportService2.cpp
netwerk/base/src/nsSocketTransportService2.h
netwerk/dns/nsDNSService2.cpp
netwerk/dns/nsDNSService2.h
netwerk/dns/nsHostResolver.cpp
netwerk/dns/nsHostResolver.h
netwerk/dns/nsIDNSService.idl
netwerk/dns/nsPIDNSService.idl
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
toolkit/components/places/tests/browser/browser_bug680727.js
toolkit/mozapps/extensions/test/xpinstall/browser_offline.js
--- a/browser/base/content/test/browser_bug435325.js
+++ b/browser/base/content/test/browser_bug435325.js
@@ -1,21 +1,29 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* Ensure that clicking the button in the Offline mode neterror page makes the browser go online. See bug 435325. */
 
+let proxyPrefValue;
+
 function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   window.addEventListener("DOMContentLoaded", checkPage, false);
 
-  // Go offline and disable the cache, then try to load the test URL.
+  // Go offline and disable the proxy and cache, then try to load the test URL.
   Services.io.offline = true;
+
+  // Tests always connect to localhost, and per bug 87717, localhost is now
+  // reachable in offline mode.  To avoid this, disable any proxy.
+  proxyPrefValue = Services.prefs.getIntPref("network.proxy.type");
+  Services.prefs.setIntPref("network.proxy.type", 0);
+
   Services.prefs.setBoolPref("browser.cache.disk.enable", false);
   Services.prefs.setBoolPref("browser.cache.memory.enable", false);
   content.location = "http://example.com/";
 }
 
 function checkPage() {
   if(content.location == "about:blank") {
     info("got about:blank, which is expected once, so return");
@@ -35,13 +43,14 @@ function checkPage() {
 
   ok(!Services.io.offline, "After clicking the Try Again button, we're back " +
                            "online.");
 
   finish();
 }
 
 registerCleanupFunction(function() {
+  Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
   Services.prefs.setBoolPref("browser.cache.disk.enable", true);
   Services.prefs.setBoolPref("browser.cache.memory.enable", true);
   Services.io.offline = false;
   gBrowser.removeCurrentTab();
 });
--- a/content/base/test/unit/test_error_codes.js
+++ b/content/base/test/unit/test_error_codes.js
@@ -35,17 +35,17 @@ function run_test_pt1() {
 
   try {
     ioService.manageOfflineStatus = false;
   }
   catch (e) {
   }
   ioService.offline = true;
 
-  gExpectedStatus = Components.results.NS_ERROR_DOCUMENT_NOT_CACHED;
+  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"]
--- a/netwerk/base/public/nsASocketHandler.h
+++ b/netwerk/base/public/nsASocketHandler.h
@@ -55,11 +55,18 @@ public:
 
     //
     // called when a socket is no longer under the control of the socket
     // transport service.  the socket handler may close the socket at this
     // point.  after this call returns, the handler will no longer be owned
     // by the socket transport service.
     //
     virtual void OnSocketDetached(PRFileDesc *fd) = 0;
+
+    //
+    // called to determine if the socket is for a local peer.
+    // when used for server sockets, indicates if it only accepts local
+    // connections.
+    //
+    virtual void IsLocal(bool *aIsLocal) = 0;
 };
 
 #endif // !nsASocketHandler_h__
--- a/netwerk/base/public/nsPISocketTransportService.idl
+++ b/netwerk/base/public/nsPISocketTransportService.idl
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISocketTransportService.idl"
 
 /**
  * This is a private interface used by the internals of the networking library.
  * It will never be frozen.  Do not use it in external code.
  */
-[scriptable, uuid(83123036-81c0-47cb-8d9c-bd85d29a1b3f)]
+[scriptable, uuid(32de7b6e-90c3-11e1-b57e-001fbc092072)]
 
 interface nsPISocketTransportService : nsISocketTransportService
 {
   /**
    * init/shutdown routines.
    */
   void init();
   void shutdown();
@@ -26,9 +26,14 @@ interface nsPISocketTransportService : n
    */
   attribute boolean autodialEnabled;
 
   /**
    * controls the TCP sender window clamp
    */
   readonly attribute long sendBufferSize;
 
+  /**
+   * Controls whether the socket transport service is offline.
+   * Setting it offline will cause non-local socket detachment.
+   */
+  attribute boolean offline;
 };
--- a/netwerk/base/src/nsIOService.cpp
+++ b/netwerk/base/src/nsIOService.cpp
@@ -240,16 +240,17 @@ nsIOService::InitializeSocketTransportSe
             NS_WARNING("failed to get socket transport service");
         }
     }
 
     if (mSocketTransportService) {
         rv = mSocketTransportService->Init();
         NS_ASSERTION(NS_SUCCEEDED(rv), "socket transport service init failed");
         mSocketTransportService->SetAutodialEnabled(mAutoDialEnabled);
+        mSocketTransportService->SetOffline(false);
     }
 
     return rv;
 }
 
 nsresult
 nsIOService::InitializeNetworkLinkService()
 {
@@ -737,70 +738,79 @@ nsIOService::SetOffline(bool offline)
         if (observerService) {
             (void)observerService->NotifyObservers(nullptr,
                 NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC, offline ? 
                 NS_LITERAL_STRING("true").get() :
                 NS_LITERAL_STRING("false").get());
         }
     }
 
+    nsIIOService *subject = static_cast<nsIIOService *>(this);
+    nsresult rv;
     while (mSetOfflineValue != mOffline) {
         offline = mSetOfflineValue;
 
-        nsresult rv;
         if (offline && !mOffline) {
             NS_NAMED_LITERAL_STRING(offlineString, NS_IOSERVICE_OFFLINE);
             mOffline = true; // indicate we're trying to shutdown
 
-            // don't care if notification fails
-            // this allows users to attempt a little cleanup before dns and socket transport are shut down.
+            // don't care if notifications fail
             if (observerService)
-                observerService->NotifyObservers(static_cast<nsIIOService *>(this),
+                observerService->NotifyObservers(subject,
                                                  NS_IOSERVICE_GOING_OFFLINE_TOPIC,
                                                  offlineString.get());
 
-            // be sure to try and shutdown both (even if the first fails)...
-            // shutdown dns service first, because it has callbacks for socket transport
-            if (mDNSService) {
-                rv = mDNSService->Shutdown();
-                NS_ASSERTION(NS_SUCCEEDED(rv), "DNS service shutdown failed");
-            }
-            if (mSocketTransportService) {
-                rv = mSocketTransportService->Shutdown();
-                NS_ASSERTION(NS_SUCCEEDED(rv), "socket transport service shutdown failed");
-            }
+            if (mDNSService)
+                mDNSService->SetOffline(true);
 
-            // don't care if notification fails
+            if (mSocketTransportService)
+                mSocketTransportService->SetOffline(true);
+
             if (observerService)
-                observerService->NotifyObservers(static_cast<nsIIOService *>(this),
+                observerService->NotifyObservers(subject,
                                                  NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
                                                  offlineString.get());
         }
         else if (!offline && mOffline) {
             // go online
             if (mDNSService) {
+                mDNSService->SetOffline(false);
                 rv = mDNSService->Init();
                 NS_ASSERTION(NS_SUCCEEDED(rv), "DNS service init failed");
             }
             InitializeSocketTransportService();
             mOffline = false;    // indicate success only AFTER we've
                                     // brought up the services
 
             // trigger a PAC reload when we come back online
             if (mProxyService)
                 mProxyService->ReloadPAC();
 
             // don't care if notification fails
             if (observerService)
-                observerService->NotifyObservers(static_cast<nsIIOService *>(this),
+                observerService->NotifyObservers(subject,
                                                  NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
                                                  NS_LITERAL_STRING(NS_IOSERVICE_ONLINE).get());
         }
     }
 
+    // Don't notify here, as the above notifications (if used) suffice.
+    if (mShutdown && mOffline) {
+        // be sure to try and shutdown both (even if the first fails)...
+        // shutdown dns service first, because it has callbacks for socket transport
+        if (mDNSService) {
+            rv = mDNSService->Shutdown();
+            NS_ASSERTION(NS_SUCCEEDED(rv), "DNS service shutdown failed");
+        }
+        if (mSocketTransportService) {
+            rv = mSocketTransportService->Shutdown();
+            NS_ASSERTION(NS_SUCCEEDED(rv), "socket transport service shutdown failed");
+        }
+    }
+
     mSettingOffline = false;
 
     return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsIOService::AllowPort(int32_t inPort, const char *scheme, bool *_retval)
--- a/netwerk/base/src/nsServerSocket.cpp
+++ b/netwerk/base/src/nsServerSocket.cpp
@@ -217,16 +217,22 @@ nsServerSocket::OnSocketDetached(PRFileD
     }
     // XXX we need to proxy the release to the listener's target thread to work
     // around bug 337492.
     if (listener)
       NS_ProxyRelease(mListenerTarget, listener);
   }
 }
 
+void
+nsServerSocket::IsLocal(bool *aIsLocal)
+{
+  // If bound to loopback, this server socket only accepts local connections.
+  *aIsLocal = PR_IsNetAddrType(&mAddr, PR_IpAddrLoopback);
+}
 
 //-----------------------------------------------------------------------------
 // nsServerSocket::nsISupports
 //-----------------------------------------------------------------------------
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsServerSocket, nsIServerSocket)
 
 
--- a/netwerk/base/src/nsServerSocket.h
+++ b/netwerk/base/src/nsServerSocket.h
@@ -17,16 +17,17 @@ class nsServerSocket : public nsASocketH
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSISERVERSOCKET
 
   // nsASocketHandler methods:
   virtual void OnSocketReady(PRFileDesc *fd, int16_t outFlags);
   virtual void OnSocketDetached(PRFileDesc *fd);
+  virtual void IsLocal(bool *aIsLocal);
 
   nsServerSocket();
 
   // This must be public to support older compilers (xlC_r on AIX)
   virtual ~nsServerSocket();
 
 private:
   void OnMsgClose();
--- a/netwerk/base/src/nsSocketTransport2.cpp
+++ b/netwerk/base/src/nsSocketTransport2.cpp
@@ -1043,16 +1043,20 @@ nsSocketTransport::BuildSocket(PRFileDes
 
 nsresult
 nsSocketTransport::InitiateSocket()
 {
     SOCKET_LOG(("nsSocketTransport::InitiateSocket [this=%x]\n", this));
 
     nsresult rv;
 
+    if (gIOService->IsOffline() &&
+        !PR_IsNetAddrType(&mNetAddr, PR_IpAddrLoopback))
+        return NS_ERROR_OFFLINE;
+
     //
     // find out if it is going to be ok to attach another socket to the STS.
     // if not then we have to wait for the STS to tell us that it is ok.
     // the notification is asynchronous, which means that when we could be
     // in a race to call AttachSocket once notified.  for this reason, when
     // we get notified, we just re-enter this function.  as a result, we are
     // sure to ask again before calling AttachSocket.  in this way we deal
     // with the race condition.  though it isn't the most elegant solution,
@@ -1659,16 +1663,25 @@ nsSocketTransport::OnSocketDetached(PRFi
         // round. That would lead e.g. to a broken certificate exception page.
         if (NS_FAILED(mCondition)) {
             mCallbacks.swap(ourCallbacks);
             mEventSink.swap(ourEventSink);
         }
     }
 }
 
+void
+nsSocketTransport::IsLocal(bool *aIsLocal)
+{
+    {
+        MutexAutoLock lock(mLock);
+        *aIsLocal = PR_IsNetAddrType(&mNetAddr, PR_IpAddrLoopback);
+    }
+}
+
 //-----------------------------------------------------------------------------
 // xpcom api
 
 NS_IMPL_THREADSAFE_ISUPPORTS4(nsSocketTransport,
                               nsISocketTransport,
                               nsITransport,
                               nsIDNSListener,
                               nsIClassInfo)
--- a/netwerk/base/src/nsSocketTransport2.h
+++ b/netwerk/base/src/nsSocketTransport2.h
@@ -121,16 +121,17 @@ public:
     // this method instructs the socket transport to use an already connected
     // socket with the given address.
     nsresult InitWithConnectedSocket(PRFileDesc *socketFD,
                                      const PRNetAddr *addr);
 
     // nsASocketHandler methods:
     void OnSocketReady(PRFileDesc *, int16_t outFlags); 
     void OnSocketDetached(PRFileDesc *);
+    void IsLocal(bool *aIsLocal);
 
     // called when a socket event is handled
     void OnSocketEvent(uint32_t type, nsresult status, nsISupports *param);
 
 protected:
 
     virtual ~nsSocketTransport();
 
--- a/netwerk/base/src/nsSocketTransportService2.cpp
+++ b/netwerk/base/src/nsSocketTransportService2.cpp
@@ -50,16 +50,18 @@ PRCallOnceType nsSocketTransportService:
 
 nsSocketTransportService::nsSocketTransportService()
     : mThread(nullptr)
     , mThreadEvent(nullptr)
     , mAutodialEnabled(false)
     , mLock("nsSocketTransportService::mLock")
     , mInitialized(false)
     , mShuttingDown(false)
+    , mOffline(false)
+    , mGoingOffline(false)
     , mActiveListSize(SOCKET_LIMIT_MIN)
     , mIdleListSize(SOCKET_LIMIT_MIN)
     , mActiveCount(0)
     , mIdleCount(0)
     , mSendBufferSize(0)
     , mProbedMaxCount(false)
 {
 #if defined(PR_LOGGING)
@@ -422,20 +424,16 @@ nsSocketTransportService::Init()
     }
 
     if (mInitialized)
         return NS_OK;
 
     if (mShuttingDown)
         return NS_ERROR_UNEXPECTED;
 
-    // Don't initialize inside the offline mode
-    if (gIOService->IsOffline() && !gIOService->IsComingOnline())
-        return NS_ERROR_OFFLINE;
-
     if (!mThreadEvent) {
         mThreadEvent = PR_NewPollableEvent();
         //
         // NOTE: per bug 190000, this failure could be caused by Zone-Alarm
         // or similar software.
         //
         // NOTE: per bug 191739, this failure could also be caused by lack
         // of a loopback device on Windows and OS/2 platforms (NSPR creates
@@ -512,24 +510,46 @@ nsSocketTransportService::Shutdown()
 
     mInitialized = false;
     mShuttingDown = false;
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsSocketTransportService::GetOffline(bool *offline)
+{
+    *offline = mOffline;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::SetOffline(bool offline)
+{
+    if (!mOffline && offline) {
+        // signal the socket thread to go offline, so it will detach sockets
+        mGoingOffline = true;
+        mOffline = true;
+    }
+    else if (mOffline && !offline) {
+        mOffline = false;
+    }
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsSocketTransportService::CreateTransport(const char **types,
                                           uint32_t typeCount,
                                           const nsACString &host,
                                           int32_t port,
                                           nsIProxyInfo *proxyInfo,
                                           nsISocketTransport **result)
 {
-    NS_ENSURE_TRUE(mInitialized, NS_ERROR_OFFLINE);
+    NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
     NS_ENSURE_TRUE(port >= 0 && port <= 0xFFFF, NS_ERROR_ILLEGAL_VALUE);
 
     nsSocketTransport *trans = new nsSocketTransport();
     if (!trans)
         return NS_ERROR_OUT_OF_MEMORY;
     NS_ADDREF(trans);
 
     nsresult rv = trans->Init(types, typeCount, host, port, proxyInfo);
@@ -619,45 +639,71 @@ nsSocketTransportService::Run()
 
             if (pendingEvents) {
                 NS_ProcessNextEvent(thread);
                 pendingEvents = false;
                 thread->HasPendingEvents(&pendingEvents);
             }
         } while (pendingEvents);
 
+        bool goingOffline = false;
         // now that our event queue is empty, check to see if we should exit
         {
             MutexAutoLock lock(mLock);
             if (mShuttingDown)
                 break;
+            if (mGoingOffline) {
+                mGoingOffline = false;
+                goingOffline = true;
+            }
         }
+        // Avoid potential deadlock
+        if (goingOffline)
+            Reset(true);
     }
 
     SOCKET_LOG(("STS shutting down thread\n"));
 
-    // detach any sockets
-    int32_t i;
-    for (i=mActiveCount-1; i>=0; --i)
-        DetachSocket(mActiveList, &mActiveList[i]);
-    for (i=mIdleCount-1; i>=0; --i)
-        DetachSocket(mIdleList, &mIdleList[i]);
+    // detach all sockets, including locals
+    Reset(false);
 
     // Final pass over the event queue. This makes sure that events posted by
     // socket detach handlers get processed.
     NS_ProcessPendingEvents(thread);
 
     gSocketThread = nullptr;
 
     psm::StopSSLServerCertVerificationThreads();
 
     SOCKET_LOG(("STS thread exit\n"));
     return NS_OK;
 }
 
+void
+nsSocketTransportService::Reset(bool aGuardLocals)
+{
+    // detach any sockets
+    int32_t i;
+    bool isGuarded;
+    for (i = mActiveCount - 1; i >= 0; --i) {
+        isGuarded = false;
+        if (aGuardLocals)
+            mActiveList[i].mHandler->IsLocal(&isGuarded);
+        if (!isGuarded)
+            DetachSocket(mActiveList, &mActiveList[i]);
+    }
+    for (i = mIdleCount - 1; i >= 0; --i) {
+        isGuarded = false;
+        if (aGuardLocals)
+            mIdleList[i].mHandler->IsLocal(&isGuarded);
+        if (!isGuarded)
+            DetachSocket(mIdleList, &mIdleList[i]);
+    }
+}
+
 nsresult
 nsSocketTransportService::DoPollIteration(bool wait)
 {
     SOCKET_LOG(("STS poll iter [%d]\n", wait));
 
     int32_t i, count;
 
     //
--- a/netwerk/base/src/nsSocketTransportService2.h
+++ b/netwerk/base/src/nsSocketTransportService2.h
@@ -102,16 +102,21 @@ private:
     // initialization and shutdown (any thread)
     //-------------------------------------------------------------------------
 
     Mutex         mLock;
     bool          mInitialized;
     bool          mShuttingDown;
                             // indicates whether we are currently in the
                             // process of shutting down
+    bool          mOffline;
+    bool          mGoingOffline;
+
+    // Detaches all sockets.
+    void Reset(bool aGuardLocals);
 
     //-------------------------------------------------------------------------
     // socket lists (socket thread only)
     //
     // only "active" sockets are on the poll list.  the active list is kept
     // in sync with the poll list such that:
     //
     //   mActiveList[k].mFD == mPollList[k+1].fd
--- a/netwerk/dns/nsDNSService2.cpp
+++ b/netwerk/dns/nsDNSService2.cpp
@@ -345,31 +345,34 @@ nsDNSSyncRequest::EqualsAsyncListener(ns
     return false;
 }
 
 //-----------------------------------------------------------------------------
 
 nsDNSService::nsDNSService()
     : mLock("nsDNSServer.mLock")
     , mFirstTime(true)
+    , mOffline(false)
 {
 }
 
 nsDNSService::~nsDNSService()
 {
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS3(nsDNSService, nsIDNSService, nsPIDNSService,
                               nsIObserver)
 
 NS_IMETHODIMP
 nsDNSService::Init()
 {
     NS_TIME_FUNCTION;
 
+    if (mResolver)
+        return NS_OK;
     NS_ENSURE_TRUE(!mResolver, NS_ERROR_ALREADY_INITIALIZED);
 
     // prefs
     uint32_t maxCacheEntries  = 400;
     uint32_t maxCacheLifetime = 2; // minutes
     uint32_t lifetimeGracePeriod = 1;
     bool     enableIDN        = true;
     bool     disableIPv6      = false;
@@ -477,16 +480,30 @@ nsDNSService::Shutdown()
         res = mResolver;
         mResolver = nullptr;
     }
     if (res)
         res->Shutdown();
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDNSService::GetOffline(bool *offline)
+{
+    *offline = mOffline;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::SetOffline(bool offline)
+{
+    mOffline = offline;
+    return NS_OK;
+}
+
 namespace {
 
 class DNSListenerProxy MOZ_FINAL : public nsIDNSListener
 {
 public:
   DNSListenerProxy(nsIDNSListener* aListener, nsIEventTarget* aTargetThread)
     : mListener(aListener)
     , mTargetThread(aTargetThread)
@@ -575,16 +592,19 @@ nsDNSService::AsyncResolve(const nsACStr
 
         res = mResolver;
         idn = mIDN;
         localDomain = mLocalDomains.GetEntry(hostname);
     }
     if (!res)
         return NS_ERROR_OFFLINE;
 
+    if (mOffline)
+        flags |= RESOLVE_OFFLINE;
+
     const nsACString *hostPtr = &hostname;
 
     if (localDomain) {
         hostPtr = &(NS_LITERAL_CSTRING("localhost"));
     }
 
     nsresult rv;
     nsAutoCString hostACE;
@@ -664,16 +684,19 @@ nsDNSService::Resolve(const nsACString &
     {
         MutexAutoLock lock(mLock);
         res = mResolver;
         idn = mIDN;
         localDomain = mLocalDomains.GetEntry(hostname);
     }
     NS_ENSURE_TRUE(res, NS_ERROR_OFFLINE);
 
+    if (mOffline)
+        flags |= RESOLVE_OFFLINE;
+
     const nsACString *hostPtr = &hostname;
 
     if (localDomain) {
         hostPtr = &(NS_LITERAL_CSTRING("localhost"));
     }
 
     nsresult rv;
     nsAutoCString hostACE;
--- a/netwerk/dns/nsDNSService2.h
+++ b/netwerk/dns/nsDNSService2.h
@@ -39,12 +39,13 @@ private:
 
     // mIPv4OnlyDomains is a comma-separated list of domains for which only
     // 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;
     nsTHashtable<nsCStringHashKey> mLocalDomains;
 };
 
 #endif //nsDNSService2_h__
--- a/netwerk/dns/nsHostResolver.cpp
+++ b/netwerk/dns/nsHostResolver.cpp
@@ -592,16 +592,20 @@ nsHostResolver::ResolveHost(const char  
             else if (mPendingCount >= MAX_NON_PRIORITY_REQUESTS &&
                      !IsHighPriority(flags) &&
                      !he->rec->resolving) {
                 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) {
+                rv = NS_ERROR_OFFLINE;
+            }
+
             // otherwise, hit the resolver...
             else {
                 // Add callback to the list of pending callbacks.
                 PR_APPEND_LINK(callback, &he->rec->callbacks);
 
                 if (!he->rec->resolving) {
                     he->rec->flags = flags;
                     rv = IssueLookup(he->rec);
--- a/netwerk/dns/nsHostResolver.h
+++ b/netwerk/dns/nsHostResolver.h
@@ -219,17 +219,19 @@ public:
      * NOTE: in this implementation, these flags correspond exactly in value
      *       to the flags defined on nsIDNSService.
      */
     enum {
         RES_BYPASS_CACHE = 1 << 0,
         RES_CANON_NAME   = 1 << 1,
         RES_PRIORITY_MEDIUM   = 1 << 2,
         RES_PRIORITY_LOW  = 1 << 3,
-        RES_SPECULATE     = 1 << 4   
+        RES_SPECULATE     = 1 << 4,
+        //RES_DISABLE_IPV6 = 1 << 5, // Not used
+        RES_OFFLINE       = 1 << 6
     };
 
 private:
     nsHostResolver(uint32_t maxCacheEntries = 50, uint32_t maxCacheLifetime = 1,
                    uint32_t lifetimeGracePeriod = 0);
    ~nsHostResolver();
 
     nsresult Init();
--- a/netwerk/dns/nsIDNSService.idl
+++ b/netwerk/dns/nsIDNSService.idl
@@ -106,9 +106,15 @@ interface nsIDNSService : nsISupports
      * return errors if prefetching is disabled by configuration.
      */
     const unsigned long RESOLVE_SPECULATE = (1 << 4);
 
     /**
      * If set, only IPv4 addresses will be returned from resolve/asyncResolve.
      */
     const unsigned long RESOLVE_DISABLE_IPV6 = (1 << 5);
+
+    /**
+     * If set, only literals and cached entries will be returned from resolve/
+     * asyncResolve.
+     */
+    const unsigned long RESOLVE_OFFLINE = (1 << 6);
 };
--- a/netwerk/dns/nsPIDNSService.idl
+++ b/netwerk/dns/nsPIDNSService.idl
@@ -5,25 +5,30 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIDNSService.idl"
 
 /**
  * This is a private interface used by the internals of the networking library.
  * It will never be frozen.  Do not use it in external code.
  */
-[scriptable, uuid(a26c5b45-7707-4412-bbc1-2462b890848d)]
+[scriptable, uuid(26bd9b9e-90c3-11e1-981c-001fbc092072)]
 interface nsPIDNSService : nsIDNSService
 {
     /**
      * called to initialize the DNS service.
      */
     void init();
 
     /**
      * called to shutdown the DNS service.  any pending asynchronous
      * requests will be canceled, and the local cache of DNS records
      * will be cleared.  NOTE: the operating system may still have
      * its own cache of DNS records, which would be unaffected by
      * this method.
      */
     void shutdown();
+
+    /**
+     * @return whether the DNS service is offline.
+     */
+    attribute boolean offline;
 };
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -402,21 +402,17 @@ nsHttpChannel::Connect()
 
     // ensure that we are using a valid hostname
     if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Host())))
         return NS_ERROR_UNKNOWN_HOST;
 
     // Consider opening a TCP connection right away
     SpeculativeConnect();
 
-    // are we offline?
-    bool offline = gIOService->IsOffline();
-    if (offline)
-        mLoadFlags |= LOAD_ONLY_FROM_CACHE;
-    else if (PL_strcmp(mConnectionInfo->ProxyType(), "unknown") == 0)
+    if (PL_strcmp(mConnectionInfo->ProxyType(), "unknown") == 0)
         return ResolveProxy();  // Lazily resolve proxy info
 
     // Don't allow resuming when cache must be used
     if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
         LOG(("Resuming from cache is not supported yet"));
         return NS_ERROR_DOCUMENT_NOT_CACHED;
     }
 
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -73,29 +73,23 @@ nsHttpConnectionMgr::nsHttpConnectionMgr
 nsHttpConnectionMgr::~nsHttpConnectionMgr()
 {
     LOG(("Destroying nsHttpConnectionMgr @%x\n", this));
     if (mTimeoutTick)
         mTimeoutTick->Cancel();
 }
 
 nsresult
-nsHttpConnectionMgr::EnsureSocketThreadTargetIfOnline()
+nsHttpConnectionMgr::EnsureSocketThreadTarget()
 {
     nsresult rv;
     nsCOMPtr<nsIEventTarget> sts;
     nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
-    if (NS_SUCCEEDED(rv)) {
-        bool offline = true;
-        ioService->GetOffline(&offline);
-
-        if (!offline) {
-            sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
-        }
-    }
+    if (NS_SUCCEEDED(rv))
+        sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
 
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
     // do nothing if already initialized or if we've shut down
     if (mSocketThreadTarget || mIsShuttingDown)
         return NS_OK;
 
     mSocketThreadTarget = sts;
@@ -121,17 +115,17 @@ nsHttpConnectionMgr::Init(uint16_t maxCo
         mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
         mMaxRequestDelay = maxRequestDelay;
         mMaxPipelinedRequests = maxPipelinedRequests;
         mMaxOptimisticPipelinedRequests = maxOptimisticPipelinedRequests;
 
         mIsShuttingDown = false;
     }
 
-    return EnsureSocketThreadTargetIfOnline();
+    return EnsureSocketThreadTarget();
 }
 
 nsresult
 nsHttpConnectionMgr::Shutdown()
 {
     LOG(("nsHttpConnectionMgr::Shutdown\n"));
 
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
@@ -156,21 +150,17 @@ nsHttpConnectionMgr::Shutdown()
     // wait for shutdown event to complete
     mon.Wait();
     return NS_OK;
 }
 
 nsresult
 nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler, int32_t iparam, void *vparam)
 {
-    // This object doesn't get reinitialized if the offline state changes, so our
-    // socket thread target might be uninitialized if we were offline when this
-    // object was being initialized, and we go online later on.  This call takes
-    // care of initializing the socket thread target if that's the case.
-    EnsureSocketThreadTargetIfOnline();
+    EnsureSocketThreadTarget();
 
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
     nsresult rv;
     if (!mSocketThreadTarget) {
         NS_WARNING("cannot post event if not initialized");
         rv = NS_ERROR_NOT_INITIALIZED;
     }
@@ -333,21 +323,17 @@ nsHttpConnectionMgr::SpeculativeConnect(
     if (NS_SUCCEEDED(rv))
         trans.forget();
     return rv;
 }
 
 nsresult
 nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target)
 {
-    // This object doesn't get reinitialized if the offline state changes, so our
-    // socket thread target might be uninitialized if we were offline when this
-    // object was being initialized, and we go online later on.  This call takes
-    // care of initializing the socket thread target if that's the case.
-    EnsureSocketThreadTargetIfOnline();
+    EnsureSocketThreadTarget();
 
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     NS_IF_ADDREF(*target = mSocketThreadTarget);
     return NS_OK;
 }
 
 nsresult
 nsHttpConnectionMgr::ReclaimConnection(nsHttpConnection *conn)
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -475,17 +475,17 @@ private:
                                          uint8_t,
                                          nsHttpConnection *,
                                          int32_t);
     nsresult BuildPipeline(nsConnectionEntry *,
                            nsAHttpTransaction *,
                            nsHttpPipeline **);
     bool     RestrictConnections(nsConnectionEntry *);
     nsresult ProcessNewTransaction(nsHttpTransaction *);
-    nsresult EnsureSocketThreadTargetIfOnline();
+    nsresult EnsureSocketThreadTarget();
     void     ClosePersistentConnections(nsConnectionEntry *ent);
     nsresult CreateTransport(nsConnectionEntry *, nsAHttpTransaction *,
                              uint8_t, bool);
     void     AddActiveConn(nsHttpConnection *, nsConnectionEntry *);
     void     StartedConnect();
     void     RecvdConnect();
 
     nsConnectionEntry *GetOrCreateConnectionEntry(nsHttpConnectionInfo *);
--- a/toolkit/components/places/tests/browser/browser_bug680727.js
+++ b/toolkit/components/places/tests/browser/browser_bug680727.js
@@ -6,21 +6,28 @@
 /* TEST_PATH=toolkit/components/places/tests/browser/browser_bug680727.js make -C $(OBJDIR) mochitest-browser-chrome */
 
 
 const kUniqueURI = Services.io.newURI("http://mochi.test:8888/#bug_680727",
                                       null, null);
 var gAsyncHistory = 
   Cc["@mozilla.org/browser/history;1"].getService(Ci.mozIAsyncHistory);
 
+let proxyPrefValue;
+
 function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
 
+  // Tests always connect to localhost, and per bug 87717, localhost is now
+  // reachable in offline mode.  To avoid this, disable any proxy.
+  proxyPrefValue = Services.prefs.getIntPref("network.proxy.type");
+  Services.prefs.setIntPref("network.proxy.type", 0);
+
   // Clear network cache.
   Components.classes["@mozilla.org/network/cache-service;1"]
             .getService(Components.interfaces.nsICacheService)
             .evictEntries(Components.interfaces.nsICache.STORE_ANYWHERE);
 
   // Go offline, expecting the error page.
   Services.io.offline = true;
   window.addEventListener("DOMContentLoaded", errorListener, false);
@@ -52,16 +59,18 @@ function errorListener() {
     gAsyncHistory.isURIVisited(kUniqueURI, errorAsyncListener);
   });
 }
 
 function errorAsyncListener(aURI, aIsVisited) {
   ok(kUniqueURI.equals(aURI) && !aIsVisited,
      "The neterror page is not listed in global history.");
 
+  Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
+
   // Now press the "Try Again" button, with offline mode off.
   Services.io.offline = false;
 
   window.addEventListener("DOMContentLoaded", reloadListener, false);
 
   ok(gBrowser.contentDocument.getElementById("errorTryAgain"),
      "The error page has got a #errorTryAgain element");
   gBrowser.contentDocument.getElementById("errorTryAgain").click();
@@ -88,13 +97,14 @@ function reloadListener() {
 }
 
 function reloadAsyncListener(aURI, aIsVisited) {
   ok(kUniqueURI.equals(aURI) && aIsVisited, "We have visited the URI.");
   waitForClearHistory(finish);
 }
 
 registerCleanupFunction(function() {
+  Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
   Services.io.offline = false;
   window.removeEventListener("DOMContentLoaded", errorListener, false);
   window.removeEventListener("DOMContentLoaded", reloadListener, false);
   gBrowser.removeCurrentTab();
 });
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_offline.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_offline.js
@@ -1,8 +1,10 @@
+let proxyPrefValue;
+
 // ----------------------------------------------------------------------------
 // Tests that going offline cancels an in progress download.
 function test() {
   Harness.downloadProgressCallback = download_progress;
   Harness.installsCompletedCallback = finish_test;
   Harness.setup();
 
   var pm = Services.perms;
@@ -12,16 +14,20 @@ function test() {
     "Unsigned XPI": TESTROOT + "unsigned.xpi"
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 }
 
 function download_progress(addon, value, maxValue) {
   try {
+    // Tests always connect to localhost, and per bug 87717, localhost is now
+    // reachable in offline mode.  To avoid this, disable any proxy.
+    proxyPrefValue = Services.prefs.getIntPref("network.proxy.type");
+    Services.prefs.setIntPref("network.proxy.type", 0);
     Services.io.manageOfflineStatus = false;
     Services.io.offline = true;
   } catch (ex) {
   }
 }
 
 function finish_test(count) {
   function wait_for_online() {
@@ -39,16 +45,17 @@ function finish_test(count) {
         Harness.finish();
       }
     }, true);
     tab.linkedBrowser.loadURI("http://example.com/");
   }
 
   is(count, 0, "No add-ons should have been installed");
   try {
+    Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
     Services.io.offline = false;
   } catch (ex) {
   }
 
   Services.perms.remove("example.com", "install");
 
   wait_for_online();
 }