Bug 786419 - Part 1 - Provide way to "set network offline" per app r=jduell
authorValentin Gosu <valentin.gosu@gmail.com>
Sat, 23 Aug 2014 06:05:56 +0300
changeset 209168 7bcb80591690a0b70b8ee1854a7924cddc688003
parent 209167 349506d5ebd58244de004897028894e953aa2e0d
child 209169 9474c394d41be2504d3748491d3ab4a7c51b1cf7
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersjduell
bugs786419
milestone35.0a1
Bug 786419 - Part 1 - Provide way to "set network offline" per app r=jduell
netwerk/base/public/nsIIOService.idl
netwerk/base/src/OfflineObserver.cpp
netwerk/base/src/OfflineObserver.h
netwerk/base/src/moz.build
netwerk/base/src/nsIOService.cpp
netwerk/base/src/nsIOService.h
netwerk/ipc/NeckoChild.cpp
netwerk/ipc/NeckoChild.h
netwerk/ipc/NeckoParent.cpp
netwerk/ipc/NeckoParent.h
netwerk/ipc/PNecko.ipdl
netwerk/protocol/ftp/FTPChannelParent.cpp
netwerk/protocol/ftp/FTPChannelParent.h
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/http/HttpChannelParent.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/websocket/WebSocketChannelParent.cpp
netwerk/protocol/websocket/WebSocketChannelParent.h
--- a/netwerk/base/public/nsIIOService.idl
+++ b/netwerk/base/public/nsIIOService.idl
@@ -84,16 +84,34 @@ interface nsIIOService : nsISupports
      * available -- that's hard to detect without causing the dialer to 
      * come up).
      *
      * Changing this fires observer notifications ... see below.
      */
     attribute boolean offline;
 
     /**
+     * Set whether network appears to be offline for network connections from
+     * a given appID.
+     *
+     * Calling this function may fire the "network:app-offline-status-changed"
+     * notification, which is also sent to child processes containing this appId.
+     * 'state' must one of nsIAppOfflineInfo::{ONLINE|OFFLINE|WIFI_ONLY}.
+     */
+    void setAppOffline(in uint32_t appId, in long state);
+
+    /**
+     * Returns true if given appId is currently not allowed to make network
+     * connections. It will return true if the app is in the wifi-only state
+     * and we are currently on a 3G connection.
+     */
+    boolean isAppOffline(in uint32_t appId);
+
+
+    /**
      * Checks if a port number is banned. This involves consulting a list of
      * unsafe ports, corresponding to network services that may be easily
      * exploitable. If the given port is considered unsafe, then the protocol
      * handler (corresponding to aScheme) will be asked whether it wishes to
      * override the IO service's decision to block the port. This gives the
      * protocol handler ultimate control over its own security policy while
      * ensuring reasonable, default protection.
      *
@@ -112,16 +130,28 @@ interface nsIIOService : nsISupports
      * @param aSpec the URL string to parse
      * @return URL scheme
      *
      * @throws NS_ERROR_MALFORMED_URI if URL string is not of the right form.
      */
     ACString extractScheme(in AUTF8String urlString);
 };
 
+[scriptable, uuid(4ac296a0-ca1b-44f4-8787-117a88cb70fb)]
+interface nsIAppOfflineInfo : nsISupports
+{
+    readonly attribute unsigned long appId;
+
+    const long ONLINE = 1;
+    const long OFFLINE = 2;
+    const long WIFI_ONLY = 3;
+
+    readonly attribute long mode;
+};
+
 %{C++
 /**
  * We send notifications through nsIObserverService with topic
  * NS_IOSERVICE_GOING_OFFLINE_TOPIC and data NS_IOSERVICE_OFFLINE
  * when 'offline' has changed from false to true, and we are about
  * to shut down network services such as DNS. When those
  * services have been shut down, we send a notification with
  * topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data
@@ -131,9 +161,15 @@ interface nsIIOService : nsISupports
  * network services have been restarted, we send a notification
  * with topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data
  * NS_IOSERVICE_ONLINE.
  */
 #define NS_IOSERVICE_GOING_OFFLINE_TOPIC  "network:offline-about-to-go-offline"
 #define NS_IOSERVICE_OFFLINE_STATUS_TOPIC "network:offline-status-changed"
 #define NS_IOSERVICE_OFFLINE              "offline"
 #define NS_IOSERVICE_ONLINE               "online"
+
+/**
+ * When network:app-offline-status-changed is fired,
+ * the 'Subject' argument is a nsIOfflineAppInfo.
+ */
+#define NS_IOSERVICE_APP_OFFLINE_STATUS_TOPIC "network:app-offline-status-changed"
 %}
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/OfflineObserver.cpp
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 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 "OfflineObserver.h"
+#include "nsNetUtil.h"
+#include "nsIOService.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h"
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(OfflineObserver, nsIObserver)
+
+void
+OfflineObserver::RegisterOfflineObserver()
+{
+  if (NS_IsMainThread()) {
+    RegisterOfflineObserverMainThread();
+  } else {
+    nsRefPtr<nsIRunnable> event =
+      NS_NewRunnableMethod(this, &OfflineObserver::RegisterOfflineObserverMainThread);
+    NS_DispatchToMainThread(event);
+  }
+}
+
+void
+OfflineObserver::RemoveOfflineObserver()
+{
+  if (NS_IsMainThread()) {
+    RemoveOfflineObserverMainThread();
+  } else {
+    nsRefPtr<nsIRunnable> event =
+      NS_NewRunnableMethod(this, &OfflineObserver::RemoveOfflineObserverMainThread);
+    NS_DispatchToMainThread(event);
+  }
+}
+
+void
+OfflineObserver::RegisterOfflineObserverMainThread()
+{
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  if (!observerService) {
+    return;
+  }
+  nsresult rv = observerService->AddObserver(this,
+    NS_IOSERVICE_APP_OFFLINE_STATUS_TOPIC, false);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to register observer");
+  }
+}
+
+void
+OfflineObserver::RemoveOfflineObserverMainThread()
+{
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  if (observerService) {
+    observerService->RemoveObserver(this, NS_IOSERVICE_APP_OFFLINE_STATUS_TOPIC);
+  }
+}
+
+OfflineObserver::OfflineObserver(DisconnectableParent * parent)
+{
+  mParent = parent;
+  RegisterOfflineObserver();
+}
+
+void
+OfflineObserver::RemoveObserver()
+{
+  RemoveOfflineObserver();
+  mParent = nullptr;
+}
+
+NS_IMETHODIMP
+OfflineObserver::Observe(nsISupports *aSubject,
+                         const char *aTopic,
+                         const char16_t *aData)
+{
+  if (mParent &&
+      !strcmp(aTopic, NS_IOSERVICE_APP_OFFLINE_STATUS_TOPIC)) {
+    mParent->OfflineNotification(aSubject);
+  }
+  return NS_OK;
+}
+
+uint32_t
+DisconnectableParent::GetAppId()
+{
+  return NECKO_UNKNOWN_APP_ID;
+}
+
+nsresult
+DisconnectableParent::OfflineNotification(nsISupports *aSubject)
+{
+  nsCOMPtr<nsIAppOfflineInfo> info(do_QueryInterface(aSubject));
+  if (!info) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  uint32_t targetAppId = NECKO_UNKNOWN_APP_ID;
+  info->GetAppId(&targetAppId);
+
+  // Obtain App ID
+  uint32_t appId = GetAppId();
+  if (appId != targetAppId) {
+    return NS_OK;
+  }
+
+  // If the app is offline, close the socket
+  if (NS_IsAppOffline(appId)) {
+    OfflineDisconnect();
+  }
+
+  return NS_OK;
+}
+
+} // net namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/OfflineObserver.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 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/. */
+
+#ifndef nsOfflineObserver_h__
+#define nsOfflineObserver_h__
+
+#include "nsIObserver.h"
+
+namespace mozilla {
+namespace net {
+
+/**
+ * Parents should extend this class and have a nsRefPtr<OfflineObserver> member.
+ * The constructor should initialize the member to new OfflineObserver(this)
+ * and the destructor should call RemoveObserver on the member.
+ *
+ * GetAppId and OfflineDisconnect are called from the default implementation
+ * of OfflineNotification. These should be overridden by classes that don't
+ * provide an implementation of OfflineNotification.
+ */
+class DisconnectableParent
+{
+public:
+  // This is called on the main thread, by the OfflineObserver.
+  // aSubject is of type nsAppOfflineInfo and contains appId and offline mode.
+  virtual nsresult OfflineNotification(nsISupports *aSubject);
+
+  // GetAppId returns the appId for the app associated with the parent
+  virtual uint32_t GetAppId();
+
+  // OfflineDisconnect cancels all existing connections in the parent when
+  // the app becomes offline.
+  virtual void     OfflineDisconnect() { }
+};
+
+/**
+ * This class observes the "network:app-offline-status-changed" topic and calls
+ * OfflineNotification on the DisconnectableParent with the subject.
+ */
+class OfflineObserver
+  : public nsIObserver
+{
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+public:
+  // A nsRefPtr to this object should be kept by the disconnectable parent.
+
+  OfflineObserver(DisconnectableParent * parent);
+  // This method needs to be called in the destructor of the parent
+  // It removes the observer from the nsObserverService list, and it clears
+  // the pointer it holds to the disconnectable parent.
+  void RemoveObserver();
+private:
+
+  // These methods are called to register and unregister the observer.
+  // If they are called on the main thread they register the observer right
+  // away, otherwise they dispatch and event to the main thread
+  void RegisterOfflineObserver();
+  void RemoveOfflineObserver();
+  void RegisterOfflineObserverMainThread();
+  void RemoveOfflineObserverMainThread();
+private:
+  virtual ~OfflineObserver() { }
+  DisconnectableParent * mParent;
+};
+
+} // net namespace
+} // mozilla namespace
+
+#endif // nsOfflineObserver_h__
--- a/netwerk/base/src/moz.build
+++ b/netwerk/base/src/moz.build
@@ -12,16 +12,17 @@ EXPORTS += [
     'nsURLParsers.h',
 ]
 
 EXPORTS.mozilla.net += [
     'ChannelDiverterChild.h',
     'ChannelDiverterParent.h',
     'Dashboard.h',
     'DashboardTypes.h',
+    'OfflineObserver.h',
 ]
 
 UNIFIED_SOURCES += [
     'ArrayBufferInputStream.cpp',
     'BackgroundFileSaver.cpp',
     'ChannelDiverterChild.cpp',
     'ChannelDiverterParent.cpp',
     'Dashboard.cpp',
@@ -80,16 +81,17 @@ UNIFIED_SOURCES += [
     'TLSServerSocket.cpp',
 ]
 
 # These files cannot be built in unified mode because they force NSPR logging.
 SOURCES += [
     'nsAsyncRedirectVerifyHelper.cpp',
     'nsSocketTransport2.cpp',
     'nsSocketTransportService2.cpp',
+    'OfflineObserver.cpp',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     SOURCES += [
         'nsAutodialWin.cpp',
         'nsNativeConnectionHelper.cpp',
         'nsURLHelperWin.cpp',
     ]
--- a/netwerk/base/src/nsIOService.cpp
+++ b/netwerk/base/src/nsIOService.cpp
@@ -23,27 +23,32 @@
 #include "nsNetCID.h"
 #include "nsCRT.h"
 #include "nsSimpleNestedURI.h"
 #include "nsNetUtil.h"
 #include "nsTArray.h"
 #include "nsIConsoleService.h"
 #include "nsIUploadChannel2.h"
 #include "nsXULAppAPI.h"
+#include "nsIScriptSecurityManager.h"
 #include "nsIProtocolProxyCallback.h"
 #include "nsICancelable.h"
 #include "nsINetworkLinkService.h"
 #include "nsPISocketTransportService.h"
 #include "nsAsyncRedirectVerifyHelper.h"
 #include "nsURLHelper.h"
 #include "nsPIDNSService.h"
 #include "nsIProtocolProxyService2.h"
 #include "MainThreadUtils.h"
 #include "nsIWidget.h"
 
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkManager.h"
+#endif
+
 #if defined(XP_WIN)
 #include "nsNativeConnectionHelper.h"
 #endif
 
 using namespace mozilla;
 
 #define PORT_PREF_PREFIX           "network.security.ports."
 #define PORT_PREF(x)               PORT_PREF_PREFIX x
@@ -128,34 +133,38 @@ int16_t gBadPortList[] = {
   4045, // lockd
   6000, // x11        
   0,    // This MUST be zero so that we can populating the array
 };
 
 static const char kProfileChangeNetTeardownTopic[] = "profile-change-net-teardown";
 static const char kProfileChangeNetRestoreTopic[] = "profile-change-net-restore";
 static const char kProfileDoChange[] = "profile-do-change";
+static const char kNetworkActiveChanged[] = "network-active-changed";
 
 // Necko buffer defaults
 uint32_t   nsIOService::gDefaultSegmentSize = 4096;
 uint32_t   nsIOService::gDefaultSegmentCount = 24;
 
+NS_IMPL_ISUPPORTS(nsAppOfflineInfo, nsIAppOfflineInfo)
+
 ////////////////////////////////////////////////////////////////////////////////
 
 nsIOService::nsIOService()
     : mOffline(true)
     , mOfflineForProfileChange(false)
     , mManageOfflineStatus(false)
     , mSettingOffline(false)
     , mSetOfflineValue(false)
     , mShutdown(false)
     , mNetworkLinkServiceInitialized(false)
     , mChannelEventSinks(NS_CHANNEL_EVENT_SINK_CATEGORY)
     , mAutoDialEnabled(false)
     , mNetworkNotifyChanged(true)
+    , mPreviousWifiState(-1)
 {
 }
 
 nsresult
 nsIOService::Init()
 {
     nsresult rv;
 
@@ -199,16 +208,17 @@ nsIOService::Init()
         mozilla::services::GetObserverService();
     if (observerService) {
         observerService->AddObserver(this, kProfileChangeNetTeardownTopic, true);
         observerService->AddObserver(this, kProfileChangeNetRestoreTopic, true);
         observerService->AddObserver(this, kProfileDoChange, true);
         observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
         observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, true);
         observerService->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true);
+        observerService->AddObserver(this, kNetworkActiveChanged, true);
     }
     else
         NS_WARNING("failed to get observer service");
 
     gIOService = this;
 
     InitializeNetworkLinkService();
 
@@ -909,16 +919,71 @@ nsIOService::ParsePortList(nsIPrefBranch
 
 void
 nsIOService::GetPrefBranch(nsIPrefBranch **result)
 {
     *result = nullptr;
     CallGetService(NS_PREFSERVICE_CONTRACTID, result);
 }
 
+// This returns true if wifi-only apps should have connectivity.
+static bool
+IsWifiActive()
+{
+    // We don't need to do this check inside the child process
+    if (XRE_GetProcessType() != GeckoProcessType_Default) {
+        return false;
+    }
+#ifdef MOZ_WIDGET_GONK
+    // On B2G we query the network manager for the active interface
+    nsCOMPtr<nsINetworkManager> networkManager =
+        do_GetService("@mozilla.org/network/manager;1");
+    if (!networkManager) {
+        return false;
+    }
+    nsCOMPtr<nsINetworkInterface> active;
+    networkManager->GetActive(getter_AddRefs(active));
+    if (!active) {
+        return false;
+    }
+    int32_t type;
+    if (NS_FAILED(active->GetType(&type))) {
+        return false;
+    }
+    switch (type) {
+    case nsINetworkInterface::NETWORK_TYPE_WIFI:
+    case nsINetworkInterface::NETWORK_TYPE_WIFI_P2P:
+        return true;
+    default:
+        return false;
+    }
+#else
+    // On anything else than B2G we return true so than wifi-only
+    // apps don't think they are offline.
+    return true;
+#endif
+}
+
+struct EnumeratorParams {
+    nsIOService *service;
+    int32_t     status;
+};
+
+PLDHashOperator
+nsIOService::EnumerateWifiAppsChangingState(const unsigned int &aKey,
+                                            int32_t aValue,
+                                            void *aUserArg)
+{
+    EnumeratorParams *params = reinterpret_cast<EnumeratorParams*>(aUserArg);
+    if (aValue == nsIAppOfflineInfo::WIFI_ONLY) {
+        params->service->NotifyAppOfflineStatus(aKey, params->status);
+    }
+    return PL_DHASH_NEXT;
+}
+
 // nsIObserver interface
 NS_IMETHODIMP
 nsIOService::Observe(nsISupports *subject,
                      const char *topic,
                      const char16_t *data)
 {
     if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
         nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(subject);
@@ -971,16 +1036,45 @@ nsIOService::Observe(nsISupports *subjec
         NS_ASSERTION(observerService, "The observer service should not be null");
 
         if (observerService && mNetworkNotifyChanged) {
             (void)observerService->
                 NotifyObservers(nullptr,
                                 NS_NETWORK_LINK_TOPIC,
                                 MOZ_UTF16(NS_NETWORK_LINK_DATA_CHANGED));
         }
+    } else if (!strcmp(topic, kNetworkActiveChanged)) {
+#ifdef MOZ_WIDGET_GONK
+        if (IsNeckoChild()) {
+          return NS_OK;
+        }
+        nsCOMPtr<nsINetworkInterface> interface = do_QueryInterface(subject);
+        if (!interface) {
+            return NS_ERROR_FAILURE;
+        }
+        int32_t state;
+        if (NS_FAILED(interface->GetState(&state))) {
+            return NS_ERROR_FAILURE;
+        }
+
+        bool wifiActive = IsWifiActive();
+        int32_t newWifiState = wifiActive ?
+            nsINetworkInterface::NETWORK_TYPE_WIFI :
+            nsINetworkInterface::NETWORK_TYPE_MOBILE;
+        if (mPreviousWifiState != newWifiState) {
+            // Notify wifi-only apps of their new status
+            int32_t status = wifiActive ?
+                nsIAppOfflineInfo::ONLINE : nsIAppOfflineInfo::OFFLINE;
+
+            EnumeratorParams params = {this, status};
+            mAppsOfflineStatus.EnumerateRead(EnumerateWifiAppsChangingState, &params);
+        }
+
+        mPreviousWifiState = newWifiState;
+#endif
     }
 
     return NS_OK;
 }
 
 // nsINetUtil interface
 NS_IMETHODIMP
 nsIOService::ParseContentType(const nsACString &aTypeHeader,
@@ -1283,8 +1377,148 @@ nsIOService::SpeculativeConnect(nsIURI *
     if (NS_FAILED(rv))
         return rv;
 
     nsCOMPtr<nsICancelable> cancelable;
     nsRefPtr<IOServiceProxyCallback> callback =
         new IOServiceProxyCallback(aCallbacks, this);
     return pps->AsyncResolve(aURI, 0, callback, getter_AddRefs(cancelable));
 }
+
+void
+nsIOService::NotifyAppOfflineStatus(uint32_t appId, int32_t state)
+{
+    MOZ_RELEASE_ASSERT(NS_IsMainThread(),
+            "Should be called on the main thread");
+
+    nsCOMPtr<nsIObserverService> observerService =
+        mozilla::services::GetObserverService();
+    MOZ_ASSERT(observerService, "The observer service should not be null");
+
+    if (observerService) {
+        nsRefPtr<nsAppOfflineInfo> info = new nsAppOfflineInfo(appId, state);
+        observerService->NotifyObservers(
+            info,
+            NS_IOSERVICE_APP_OFFLINE_STATUS_TOPIC,
+            MOZ_UTF16("all data in nsIAppOfflineInfo subject argument"));
+    }
+}
+
+namespace {
+
+class SetAppOfflineMainThread : public nsRunnable
+{
+public:
+    SetAppOfflineMainThread(uint32_t aAppId, int32_t aState)
+        : mAppId(aAppId)
+        , mState(aState)
+    {
+    }
+
+    NS_IMETHOD Run()
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+        gIOService->SetAppOfflineInternal(mAppId, mState);
+        return NS_OK;
+    }
+private:
+    uint32_t mAppId;
+    int32_t mState;
+};
+
+}
+
+NS_IMETHODIMP
+nsIOService::SetAppOffline(uint32_t aAppId, int32_t aState)
+{
+    NS_ENSURE_TRUE(XRE_GetProcessType() == GeckoProcessType_Default,
+                   NS_ERROR_FAILURE);
+    NS_ENSURE_TRUE(aAppId != nsIScriptSecurityManager::NO_APP_ID,
+                   NS_ERROR_INVALID_ARG);
+    NS_ENSURE_TRUE(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID,
+                   NS_ERROR_INVALID_ARG);
+
+    if (!NS_IsMainThread()) {
+        NS_DispatchToMainThread(new SetAppOfflineMainThread(aAppId, aState));
+        return NS_OK;
+    }
+
+    SetAppOfflineInternal(aAppId, aState);
+
+    return NS_OK;
+}
+
+void
+nsIOService::SetAppOfflineInternal(uint32_t aAppId, int32_t aState)
+{
+    MOZ_ASSERT(NS_IsMainThread());
+    NS_ENSURE_TRUE_VOID(NS_IsMainThread());
+
+    int32_t state;
+    if (mAppsOfflineStatus.Get(aAppId, &state) && state == aState) {
+        // The app is already in this state. Nothing needs to be done.
+        return;
+    }
+
+    bool wifiActive = IsWifiActive();
+    bool offline = (state == nsIAppOfflineInfo::OFFLINE) ||
+                   (state == nsIAppOfflineInfo::WIFI_ONLY && !wifiActive);
+
+    switch (aState) {
+    case nsIAppOfflineInfo::OFFLINE:
+        mAppsOfflineStatus.Put(aAppId, nsIAppOfflineInfo::OFFLINE);
+        if (!offline) {
+            NotifyAppOfflineStatus(aAppId, nsIAppOfflineInfo::OFFLINE);
+        }
+        break;
+    case nsIAppOfflineInfo::WIFI_ONLY:
+        mAppsOfflineStatus.Put(aAppId, nsIAppOfflineInfo::WIFI_ONLY);
+        if (offline && wifiActive) {
+            NotifyAppOfflineStatus(aAppId, nsIAppOfflineInfo::ONLINE);
+        } else if (!offline && !wifiActive) {
+            NotifyAppOfflineStatus(aAppId, nsIAppOfflineInfo::OFFLINE);
+        }
+        break;
+    case nsIAppOfflineInfo::ONLINE:
+        mAppsOfflineStatus.Remove(aAppId);
+        if (offline) {
+            NotifyAppOfflineStatus(aAppId, nsIAppOfflineInfo::ONLINE);
+        }
+        break;
+    default:
+        break;
+    }
+
+}
+
+NS_IMETHODIMP
+nsIOService::IsAppOffline(uint32_t aAppId, bool* aResult)
+{
+    NS_ENSURE_ARG(aResult);
+    *aResult = mOffline;
+
+    if (mOffline) {
+        // If the entire browser is offline, return that status
+        return NS_OK;
+    }
+
+    if (aAppId == NECKO_NO_APP_ID ||
+        aAppId == NECKO_UNKNOWN_APP_ID) {
+        return NS_ERROR_NOT_AVAILABLE;
+    }
+
+    int32_t state;
+    if (mAppsOfflineStatus.Get(aAppId, &state)) {
+        switch (state) {
+        case nsIAppOfflineInfo::OFFLINE:
+            *aResult = true;
+            break;
+        case nsIAppOfflineInfo::WIFI_ONLY:
+            *aResult = !IsWifiActive();
+            break;
+        default:
+            // The app is online by default
+            break;
+        }
+    }
+
+    return NS_OK;
+}
--- a/netwerk/base/src/nsIOService.h
+++ b/netwerk/base/src/nsIOService.h
@@ -12,16 +12,17 @@
 #include "nsCOMPtr.h"
 #include "nsWeakPtr.h"
 #include "nsIObserver.h"
 #include "nsWeakReference.h"
 #include "nsINetUtil.h"
 #include "nsIChannelEventSink.h"
 #include "nsCategoryCache.h"
 #include "nsISpeculativeConnect.h"
+#include "nsDataHashtable.h"
 #include "mozilla/Attributes.h"
 
 #define NS_N(x) (sizeof(x)/sizeof(*x))
 
 // We don't want to expose this observer topic.
 // Intended internal use only for remoting offline/inline events.
 // See Bug 552829
 #define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
@@ -32,16 +33,22 @@ static const char gScheme[][sizeof("reso
 class nsAsyncRedirectVerifyHelper;
 class nsINetworkLinkService;
 class nsIPrefBranch;
 class nsIProtocolProxyService2;
 class nsIProxyInfo;
 class nsPIDNSService;
 class nsPISocketTransportService;
 
+namespace mozilla {
+namespace net {
+    class NeckoChild;
+} // namespace net
+} // namespace mozilla
+
 class nsIOService MOZ_FINAL : public nsIIOService2
                             , public nsIObserver
                             , public nsINetUtil
                             , public nsISpeculativeConnect
                             , public nsSupportsWeakReference
 {
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
@@ -69,16 +76,19 @@ public:
 
     bool IsOffline() { return mOffline; }
     bool IsLinkUp();
 
     bool IsComingOnline() const {
       return mOffline && mSettingOffline && !mSetOfflineValue;
     }
 
+    // Should only be called from NeckoChild. Use SetAppOffline instead.
+    void SetAppOfflineInternal(uint32_t appId, int32_t status);
+
 private:
     // These shouldn't be called directly:
     // - construct using GetInstance
     // - destroy using Release
     nsIOService();
     ~nsIOService();
 
     nsresult OnNetworkLinkEvent(const char *data);
@@ -97,16 +107,21 @@ private:
 
     nsresult InitializeSocketTransportService();
     nsresult InitializeNetworkLinkService();
 
     // consolidated helper function
     void LookupProxyInfo(nsIURI *aURI, nsIURI *aProxyURI, uint32_t aProxyFlags,
                          nsCString *aScheme, nsIProxyInfo **outPI);
 
+    // notify content processes of offline status
+    // 'status' must be a nsIAppOfflineInfo mode constant.
+    void NotifyAppOfflineStatus(uint32_t appId, int32_t status);
+    static PLDHashOperator EnumerateWifiAppsChangingState(const unsigned int &, int32_t, void*);
+
 private:
     bool                                 mOffline;
     bool                                 mOfflineForProfileChange;
     bool                                 mManageOfflineStatus;
 
     // Used to handle SetOffline() reentrancy.  See the comment in
     // SetOffline() for more details.
     bool                                 mSettingOffline;
@@ -125,20 +140,57 @@ private:
 
     // cached categories
     nsCategoryCache<nsIChannelEventSink> mChannelEventSinks;
 
     nsTArray<int32_t>                    mRestrictedPortList;
 
     bool                                 mAutoDialEnabled;
     bool                                 mNetworkNotifyChanged;
+    int32_t                              mPreviousWifiState;
+    // Hashtable of (appId, nsIAppOffineInfo::mode) pairs
+    // that is used especially in IsAppOffline
+    nsDataHashtable<nsUint32HashKey, int32_t> mAppsOfflineStatus;
 public:
     // Used for all default buffer sizes that necko allocates.
     static uint32_t   gDefaultSegmentSize;
     static uint32_t   gDefaultSegmentCount;
 };
 
 /**
+ * This class is passed as the subject to a NotifyObservers call for the
+ * "network:app-offline-status-changed" topic.
+ * Observers will use the appId and mode to get the offline status of an app.
+ */
+class nsAppOfflineInfo : public nsIAppOfflineInfo
+{
+    NS_DECL_THREADSAFE_ISUPPORTS
+public:
+    nsAppOfflineInfo(uint32_t aAppId, int32_t aMode)
+        : mAppId(aAppId), mMode(aMode)
+    {
+    }
+
+    NS_IMETHODIMP GetMode(int32_t *aMode)
+    {
+        *aMode = mMode;
+        return NS_OK;
+    }
+
+    NS_IMETHODIMP GetAppId(uint32_t *aAppId)
+    {
+        *aAppId = mAppId;
+        return NS_OK;
+    }
+
+private:
+    virtual ~nsAppOfflineInfo() {}
+
+    uint32_t mAppId;
+    int32_t mMode;
+};
+
+/**
  * Reference to the IO service singleton. May be null.
  */
 extern nsIOService* gIOService;
 
 #endif // nsIOService_h__
--- a/netwerk/ipc/NeckoChild.cpp
+++ b/netwerk/ipc/NeckoChild.cpp
@@ -21,16 +21,17 @@
 #include "mozilla/dom/network/TCPSocketChild.h"
 #include "mozilla/dom/network/TCPServerSocketChild.h"
 #include "mozilla/dom/network/UDPSocketChild.h"
 #ifdef NECKO_PROTOCOL_rtsp
 #include "mozilla/net/RtspControllerChild.h"
 #include "mozilla/net/RtspChannelChild.h"
 #endif
 #include "SerializedLoadContext.h"
+#include "nsIOService.h"
 
 using mozilla::dom::TCPSocketChild;
 using mozilla::dom::TCPServerSocketChild;
 using mozilla::dom::UDPSocketChild;
 
 namespace mozilla {
 namespace net {
 
@@ -312,10 +313,22 @@ NeckoChild::RecvAsyncAuthPromptForNested
     MOZ_CRASH();
     return false;
   }
   dom::TabChild* tabChild = iter->second;
   tabChild->SendAsyncAuthPrompt(aUri, aRealm, aCallbackId);
   return true;
 }
 
+bool
+NeckoChild::RecvAppOfflineStatus(const uint32_t& aId, const bool& aOffline)
+{
+  // Instantiate the service to make sure gIOService is initialized
+  nsCOMPtr<nsIIOService> ioService = do_GetIOService();
+  if (gIOService) {
+    gIOService->SetAppOfflineInternal(aId, aOffline ?
+      nsIAppOfflineInfo::OFFLINE : nsIAppOfflineInfo::ONLINE);
+  }
+  return true;
+}
+
 }} // mozilla::net
 
--- a/netwerk/ipc/NeckoChild.h
+++ b/netwerk/ipc/NeckoChild.h
@@ -70,16 +70,17 @@ protected:
   virtual PChannelDiverterChild*
   AllocPChannelDiverterChild(const ChannelDiverterArgs& channel) MOZ_OVERRIDE;
   virtual bool
   DeallocPChannelDiverterChild(PChannelDiverterChild* actor) MOZ_OVERRIDE;
   virtual bool RecvAsyncAuthPromptForNestedFrame(const uint64_t& aNestedFrameId,
                                                  const nsCString& aUri,
                                                  const nsString& aRealm,
                                                  const uint64_t& aCallbackId) MOZ_OVERRIDE;
+  virtual bool RecvAppOfflineStatus(const uint32_t& aId, const bool& aOffline) MOZ_OVERRIDE;
 };
 
 /**
  * Reference to the PNecko Child protocol.
  * Null if this is not a content process.
  */
 extern PNeckoChild *gNeckoChild;
 
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -32,16 +32,18 @@
 #include "nsHTMLDNSPrefetch.h"
 #include "nsIAppsService.h"
 #include "nsEscape.h"
 #include "RemoteOpenFileParent.h"
 #include "SerializedLoadContext.h"
 #include "nsAuthInformationHolder.h"
 #include "nsIAuthPromptCallback.h"
 #include "nsPrincipal.h"
+#include "nsIOService.h"
+#include "mozilla/net/OfflineObserver.h"
 
 using mozilla::dom::ContentParent;
 using mozilla::dom::TabParent;
 using mozilla::net::PTCPSocketParent;
 using mozilla::dom::TCPSocketParent;
 using mozilla::net::PTCPServerSocketParent;
 using mozilla::dom::TCPServerSocketParent;
 using mozilla::net::PUDPSocketParent;
@@ -69,20 +71,25 @@ NeckoParent::NeckoParent()
       appsService->GetWebAppsBasePath(webPath);
     }
     // corePath may be empty: we don't use it for all build types
     MOZ_ASSERT(!webPath.IsEmpty());
 
     LossyCopyUTF16toASCII(corePath, mCoreAppsBasePath);
     LossyCopyUTF16toASCII(webPath, mWebAppsBasePath);
   }
+
+  mObserver = new OfflineObserver(this);
 }
 
 NeckoParent::~NeckoParent()
 {
+  if (mObserver) {
+    mObserver->RemoveObserver();
+  }
 }
 
 static PBOverrideStatus
 PBOverrideStatusFromLoadContext(const SerializedLoadContext& aSerialized)
 {
   if (!aSerialized.IsNotNull() && aSerialized.IsPrivateBitValid()) {
     return aSerialized.mUsePrivateBrowsing ?
       kPBOverride_Private :
@@ -791,9 +798,48 @@ NeckoParent::RecvOnAuthCancelled(const u
   if (!callback) {
     return true;
   }
   CallbackMap().erase(aCallbackId);
   callback->OnAuthCancelled(nullptr, aUserCancel);
   return true;
 }
 
+nsresult
+NeckoParent::OfflineNotification(nsISupports *aSubject)
+{
+  nsCOMPtr<nsIAppOfflineInfo> info(do_QueryInterface(aSubject));
+  if (!info) {
+    return NS_OK;
+  }
+
+  uint32_t targetAppId = NECKO_UNKNOWN_APP_ID;
+  info->GetAppId(&targetAppId);
+
+  for (uint32_t i = 0; i < Manager()->ManagedPBrowserParent().Length(); ++i) {
+    nsRefPtr<TabParent> tabParent =
+      static_cast<TabParent*>(Manager()->ManagedPBrowserParent()[i]);
+    uint32_t appId = tabParent->OwnOrContainingAppId();
+
+    if (appId == targetAppId) {
+      if (gIOService) {
+        bool offline = false;
+        nsresult rv = gIOService->IsAppOffline(appId, &offline);
+        if (NS_FAILED(rv)) {
+          printf_stderr("Unexpected - NeckoParent: "
+                        "appId not found by isAppOffline(): %u\n", appId);
+          break;
+        }
+        if (!SendAppOfflineStatus(appId, offline)) {
+          printf_stderr("NeckoParent: "
+                        "SendAppOfflineStatus failed for appId: %u\n", appId);
+        }
+        // Once we found the targetAppId, we don't need to continue
+        break;
+      }
+    }
+
+  }
+
+  return NS_OK;
+}
+
 }} // mozilla::net
--- a/netwerk/ipc/NeckoParent.h
+++ b/netwerk/ipc/NeckoParent.h
@@ -2,33 +2,35 @@
 /* vim: set sw=2 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 "mozilla/net/PNeckoParent.h"
 #include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/OfflineObserver.h"
 
 #ifndef mozilla_net_NeckoParent_h
 #define mozilla_net_NeckoParent_h
 
 namespace mozilla {
 namespace net {
 
 // Used to override channel Private Browsing status if needed.
 enum PBOverrideStatus {
   kPBOverride_Unset = 0,
   kPBOverride_Private,
   kPBOverride_NotPrivate
 };
 
 // Header file contents
-class NeckoParent :
-  public PNeckoParent
+class NeckoParent
+  : public PNeckoParent
+  , public DisconnectableParent
 {
 public:
   NeckoParent();
   virtual ~NeckoParent();
 
   MOZ_WARN_UNUSED_RESULT
   static const char *
   GetValidatedAppInfo(const SerializedLoadContext& aSerialized,
@@ -46,17 +48,17 @@ public:
   MOZ_WARN_UNUSED_RESULT
   static const char*
   CreateChannelLoadContext(const PBrowserOrId& aBrowser,
                            PContentParent* aContent,
                            const SerializedLoadContext& aSerialized,
                            nsCOMPtr<nsILoadContext> &aResult);
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
-
+  virtual nsresult OfflineNotification(nsISupports *) MOZ_OVERRIDE;
   virtual void
   CloneManagees(ProtocolBase* aSource,
               mozilla::ipc::ProtocolCloneContext* aCtx) MOZ_OVERRIDE;
   virtual PCookieServiceParent* AllocPCookieServiceParent() MOZ_OVERRIDE;
   virtual bool
   RecvPCookieServiceConstructor(PCookieServiceParent* aActor) MOZ_OVERRIDE
   {
     return PNeckoParent::RecvPCookieServiceConstructor(aActor);
@@ -196,14 +198,15 @@ protected:
                                    const nsString& aPassword,
                                    const nsString& aDomain) MOZ_OVERRIDE;
   virtual bool RecvOnAuthCancelled(const uint64_t& aCallbackId,
                                    const bool& aUserCancel) MOZ_OVERRIDE;
 
 private:
   nsCString mCoreAppsBasePath;
   nsCString mWebAppsBasePath;
+  nsRefPtr<OfflineObserver> mObserver;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // mozilla_net_NeckoParent_h
--- a/netwerk/ipc/PNecko.ipdl
+++ b/netwerk/ipc/PNecko.ipdl
@@ -95,16 +95,18 @@ parent:
 child:
   /*
    * Bring up the http auth prompt for a nested remote mozbrowser.
    * NestedFrameId is the id corresponding to the PBrowser.  It is the same id
    * that was passed to the PBrowserOrId param in to the PHttpChannel constructor
    */
   AsyncAuthPromptForNestedFrame(uint64_t nestedFrameId, nsCString uri,
                                 nsString realm, uint64_t callbackId);
+  // Notifies child that a given app is now offline (or online)
+  AppOfflineStatus(uint32_t appId, bool offline);
 
 both:
   // Actually we need PTCPSocket() for parent. But ipdl disallows us having different
   // signatures on parent and child. So when constructing the parent side object, we just 
   // leave host/port unused.
   PTCPSocket(nsString host, uint16_t port);
 };
 
--- a/netwerk/protocol/ftp/FTPChannelParent.cpp
+++ b/netwerk/protocol/ftp/FTPChannelParent.cpp
@@ -13,16 +13,17 @@
 #include "nsIHttpChannelInternal.h"
 #include "nsIForcePendingChannel.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/unused.h"
 #include "SerializedLoadContext.h"
 #include "nsIContentPolicy.h"
 #include "mozilla/ipc/BackgroundUtils.h"
+#include "nsIOService.h"
 
 using namespace mozilla::ipc;
 
 #undef LOG
 #define LOG(args) PR_LOG(gFTPLog, PR_LOG_DEBUG, args)
 
 namespace mozilla {
 namespace net {
@@ -34,21 +35,26 @@ FTPChannelParent::FTPChannelParent(nsILo
   , mStatus(NS_OK)
   , mDivertingFromChild(false)
   , mDivertedOnStartRequest(false)
   , mSuspendedForDiversion(false)
 {
   nsIProtocolHandler* handler;
   CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ftp", &handler);
   NS_ASSERTION(handler, "no ftp handler");
+  
+  mObserver = new OfflineObserver(this);
 }
 
 FTPChannelParent::~FTPChannelParent()
 {
   gFtpHandler->Release();
+  if (mObserver) {
+    mObserver->RemoveObserver();
+  }
 }
 
 void
 FTPChannelParent::ActorDestroy(ActorDestroyReason why)
 {
   // We may still have refcount>0 if the channel hasn't called OnStopRequest
   // yet, but we must not send any more msgs to child.
   mIPCClosed = true;
@@ -110,16 +116,27 @@ FTPChannelParent::DoAsyncOpen(const URIP
 
 #ifdef DEBUG
   nsCString uriSpec;
   uri->GetSpec(uriSpec);
   LOG(("FTPChannelParent DoAsyncOpen [this=%p uri=%s]\n",
        this, uriSpec.get()));
 #endif
 
+  bool app_offline = false;
+  uint32_t appId = GetAppId();
+  if (appId != NECKO_UNKNOWN_APP_ID &&
+      appId != NECKO_NO_APP_ID) {
+    gIOService->IsAppOffline(appId, &app_offline);
+    LOG(("FTP app id %u is offline %d\n", appId, app_offline));
+  }
+
+  if (app_offline)
+    return SendFailedAsyncOpen(NS_ERROR_OFFLINE);
+
   nsresult rv;
   nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
   if (NS_FAILED(rv))
     return SendFailedAsyncOpen(rv);
 
   nsCOMPtr<nsIPrincipal> requestingPrincipal =
     mozilla::ipc::PrincipalInfoToPrincipal(aRequestingPrincipalInfo, &rv);
   if (NS_FAILED(rv)) {
@@ -191,16 +208,17 @@ FTPChannelParent::ConnectChannel(const u
   return true;
 }
 
 bool
 FTPChannelParent::RecvCancel(const nsresult& status)
 {
   if (mChannel)
     mChannel->Cancel(status);
+
   return true;
 }
 
 bool
 FTPChannelParent::RecvSuspend()
 {
   if (mChannel)
     mChannel->Suspend();
@@ -637,16 +655,35 @@ FTPChannelParent::NotifyDiversionFailed(
   mDivertToListener = nullptr;
   mChannel = nullptr;
 
   if (!mIPCClosed) {
     unused << SendDeleteSelf();
   }
 }
 
+void
+FTPChannelParent::OfflineDisconnect()
+{
+  if (mChannel) {
+    mChannel->Cancel(NS_ERROR_OFFLINE);
+  }
+  mStatus = NS_ERROR_OFFLINE;
+}
+
+uint32_t
+FTPChannelParent::GetAppId()
+{
+  uint32_t appId = NECKO_UNKNOWN_APP_ID;
+  if (mLoadContext) {
+    mLoadContext->GetAppId(&appId);
+  }
+  return appId;
+}
+
 //-----------------------------------------------------------------------------
 // FTPChannelParent::nsIChannelEventSink
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 FTPChannelParent::AsyncOnChannelRedirect(
                             nsIChannel *oldChannel,
                             nsIChannel *newChannel,
--- a/netwerk/protocol/ftp/FTPChannelParent.h
+++ b/netwerk/protocol/ftp/FTPChannelParent.h
@@ -8,28 +8,30 @@
 #ifndef mozilla_net_FTPChannelParent_h
 #define mozilla_net_FTPChannelParent_h
 
 #include "ADivertableParentChannel.h"
 #include "mozilla/net/PFTPChannelParent.h"
 #include "mozilla/net/NeckoParent.h"
 #include "nsIParentChannel.h"
 #include "nsIInterfaceRequestor.h"
+#include "OfflineObserver.h"
 
 class nsFtpChannel;
 class nsILoadContext;
 
 namespace mozilla {
 namespace net {
 
 class FTPChannelParent : public PFTPChannelParent
                        , public nsIParentChannel
                        , public nsIInterfaceRequestor
                        , public ADivertableParentChannel
                        , public nsIChannelEventSink
+                       , public DisconnectableParent
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSIPARENTCHANNEL
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSICHANNELEVENTSINK
@@ -77,16 +79,19 @@ protected:
   virtual bool RecvDivertOnDataAvailable(const nsCString& data,
                                          const uint64_t& offset,
                                          const uint32_t& count) MOZ_OVERRIDE;
   virtual bool RecvDivertOnStopRequest(const nsresult& statusCode) MOZ_OVERRIDE;
   virtual bool RecvDivertComplete() MOZ_OVERRIDE;
 
   virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
+  void OfflineDisconnect() MOZ_OVERRIDE;
+  uint32_t GetAppId() MOZ_OVERRIDE;
+
   // if configured to use HTTP proxy for FTP, this can an an HTTP channel.
   nsCOMPtr<nsIChannel> mChannel;
 
   bool mIPCClosed;
 
   nsCOMPtr<nsILoadContext> mLoadContext;
 
   PBOverrideStatus mPBOverride;
@@ -101,14 +106,15 @@ protected:
   // received from the child channel.
   bool mDivertingFromChild;
   // Set if OnStart|StopRequest was called during a diversion from the child.
   bool mDivertedOnStartRequest;
 
   // Set if we successfully suspended the nsHttpChannel for diversion. Unset
   // when we call ResumeForDiversion.
   bool mSuspendedForDiversion;
+  nsRefPtr<OfflineObserver> mObserver;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // mozilla_net_FTPChannelParent_h
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -24,16 +24,19 @@
 #include "nsIApplicationCacheService.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "SerializedLoadContext.h"
 #include "nsIAuthInformation.h"
 #include "nsIAuthPromptCallback.h"
 #include "nsIContentPolicy.h"
 #include "mozilla/ipc/BackgroundUtils.h"
+#include "nsIOService.h"
+#include "nsICachingChannel.h"
+
 
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace net {
 
 HttpChannelParent::HttpChannelParent(const PBrowserOrId& iframeEmbedding,
@@ -61,20 +64,25 @@ HttpChannelParent::HttpChannelParent(con
   MOZ_ASSERT(gHttpHandler);
   mHttpHandler = gHttpHandler;
 
   if (iframeEmbedding.type() == PBrowserOrId::TPBrowserParent) {
     mTabParent = static_cast<dom::TabParent*>(iframeEmbedding.get_PBrowserParent());
   } else {
     mNestedFrameId = iframeEmbedding.get_uint64_t();
   }
+
+  mObserver = new OfflineObserver(this);
 }
 
 HttpChannelParent::~HttpChannelParent()
 {
+  if (mObserver) {
+    mObserver->RemoveObserver();
+  }
 }
 
 void
 HttpChannelParent::ActorDestroy(ActorDestroyReason why)
 {
   // We may still have refcount>0 if nsHttpChannel hasn't called OnStopRequest
   // yet, but child process has crashed.  We must not try to send any more msgs
   // to child, or IPDL will kill chrome process, too.
@@ -159,17 +167,17 @@ HttpChannelParent::GetInterface(const ns
 //-----------------------------------------------------------------------------
 
 bool
 HttpChannelParent::DoAsyncOpen(  const URIParams&           aURI,
                                  const OptionalURIParams&   aOriginalURI,
                                  const OptionalURIParams&   aDocURI,
                                  const OptionalURIParams&   aReferrerURI,
                                  const OptionalURIParams&   aAPIRedirectToURI,
-                                 const uint32_t&            loadFlags,
+                                 const uint32_t&            aLoadFlags,
                                  const RequestHeaderTuples& requestHeaders,
                                  const nsCString&           requestMethod,
                                  const OptionalInputStreamParams& uploadStream,
                                  const bool&              uploadStreamHasHeaders,
                                  const uint16_t&            priority,
                                  const uint8_t&             redirectionLimit,
                                  const bool&              allowPipelining,
                                  const bool&              allowSTS,
@@ -208,16 +216,28 @@ HttpChannelParent::DoAsyncOpen(  const U
     return SendFailedAsyncOpen(rv);
 
   nsCOMPtr<nsIPrincipal> requestingPrincipal =
     mozilla::ipc::PrincipalInfoToPrincipal(aRequestingPrincipalInfo, &rv);
   if (NS_FAILED(rv)) {
     return SendFailedAsyncOpen(rv);
   }
 
+  bool appOffline = false;
+  uint32_t appId = GetAppId();
+  if (appId != NECKO_UNKNOWN_APP_ID &&
+      appId != NECKO_NO_APP_ID) {
+    gIOService->IsAppOffline(appId, &appOffline);
+  }
+
+  uint32_t loadFlags = aLoadFlags;
+  if (appOffline) {
+    loadFlags |= nsICachingChannel::LOAD_ONLY_FROM_CACHE;
+  }
+
   nsCOMPtr<nsIChannel> channel;
   rv = NS_NewChannel(getter_AddRefs(channel),
                      uri,
                      requestingPrincipal,
                      aSecurityFlags,
                      aContentPolicyType,
                      nullptr,   // aChannelPolicy
                      nullptr,   // loadGroup
@@ -304,20 +324,18 @@ HttpChannelParent::DoAsyncOpen(  const U
       if (NS_SUCCEEDED(rv)) {
         appCacheChan->SetApplicationCache(appCache);
         setChooseApplicationCache = false;
       }
     }
 
     if (setChooseApplicationCache) {
       bool inBrowser = false;
-      uint32_t appId = NECKO_NO_APP_ID;
       if (mLoadContext) {
         mLoadContext->GetIsInBrowserElement(&inBrowser);
-        mLoadContext->GetAppId(&appId);
       }
 
       bool chooseAppCache = false;
       nsCOMPtr<nsIScriptSecurityManager> secMan =
         do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
       if (secMan) {
         nsCOMPtr<nsIPrincipal> principal;
         secMan->GetAppCodebasePrincipal(uri, appId, inBrowser, getter_AddRefs(principal));
@@ -352,16 +370,28 @@ HttpChannelParent::ConnectChannel(const 
   if (mPBOverride != kPBOverride_Unset) {
     // redirected-to channel may not support PB
     nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryObject(mChannel);
     if (pbChannel) {
       pbChannel->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
     }
   }
 
+  bool appOffline = false;
+  uint32_t appId = GetAppId();
+  if (appId != NECKO_UNKNOWN_APP_ID &&
+      appId != NECKO_NO_APP_ID) {
+    gIOService->IsAppOffline(appId, &appOffline);
+  }
+
+  if (appOffline) {
+    mChannel->Cancel(NS_ERROR_OFFLINE);
+    mStatus = NS_ERROR_OFFLINE;
+  }
+
   return true;
 }
 
 bool
 HttpChannelParent::RecvSetPriority(const uint16_t& priority)
 {
   if (mChannel) {
     mChannel->SetPriority(priority);
@@ -1031,16 +1061,35 @@ HttpChannelParent::NotifyDiversionFailed
   mParentListener = nullptr;
   mChannel = nullptr;
 
   if (!mIPCClosed) {
     unused << SendDeleteSelf();
   }
 }
 
+void
+HttpChannelParent::OfflineDisconnect()
+{
+  if (mChannel) {
+    mChannel->Cancel(NS_ERROR_OFFLINE);
+  }
+  mStatus = NS_ERROR_OFFLINE;
+}
+
+uint32_t
+HttpChannelParent::GetAppId()
+{
+  uint32_t appId = NECKO_UNKNOWN_APP_ID;
+  if (mLoadContext) {
+    mLoadContext->GetAppId(&appId);
+  }
+  return appId;
+}
+
 NS_IMETHODIMP
 HttpChannelParent::GetAuthPrompt(uint32_t aPromptReason, const nsIID& iid,
                                  void** aResult)
 {
   nsCOMPtr<nsIAuthPrompt2> prompt =
     new NeckoParent::NestedFrameAuthPrompt(Manager(), mNestedFrameId);
   prompt.forget(aResult);
   return NS_OK;
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -8,16 +8,18 @@
 #ifndef mozilla_net_HttpChannelParent_h
 #define mozilla_net_HttpChannelParent_h
 
 #include "ADivertableParentChannel.h"
 #include "nsHttp.h"
 #include "mozilla/net/PHttpChannelParent.h"
 #include "mozilla/net/NeckoCommon.h"
 #include "mozilla/net/NeckoParent.h"
+#include "OfflineObserver.h"
+#include "nsIObserver.h"
 #include "nsIParentRedirectingChannel.h"
 #include "nsIProgressEventSink.h"
 #include "nsHttpChannel.h"
 #include "nsIAuthPromptProvider.h"
 
 class nsICacheEntry;
 class nsIAssociatedContentSecurity;
 
@@ -33,16 +35,17 @@ class HttpChannelParentListener;
 class PBrowserOrId;
 
 class HttpChannelParent : public PHttpChannelParent
                         , public nsIParentRedirectingChannel
                         , public nsIProgressEventSink
                         , public nsIInterfaceRequestor
                         , public ADivertableParentChannel
                         , public nsIAuthPromptProvider
+                        , public DisconnectableParent
 {
   virtual ~HttpChannelParent();
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSIPARENTCHANNEL
@@ -124,16 +127,19 @@ protected:
   nsresult ResumeForDiversion();
 
   // Asynchronously calls NotifyDiversionFailed.
   void FailDiversion(nsresult aErrorCode, bool aSkipResume = true);
 
   friend class HttpChannelParentListener;
   nsRefPtr<mozilla::dom::TabParent> mTabParent;
 
+  void OfflineDisconnect() MOZ_OVERRIDE;
+  uint32_t GetAppId() MOZ_OVERRIDE;
+
 private:
   nsRefPtr<nsHttpChannel>       mChannel;
   nsCOMPtr<nsICacheEntry>       mCacheEntry;
   nsCOMPtr<nsIAssociatedContentSecurity>  mAssociatedContentSecurity;
   bool mIPCClosed;                // PHttpChannel actor has been Closed()
 
   nsCOMPtr<nsIChannel> mRedirectChannel;
   nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
@@ -145,16 +151,18 @@ private:
   nsresult mStoredStatus;
   uint64_t mStoredProgress;
   uint64_t mStoredProgressMax;
 
   bool mSentRedirect1Begin          : 1;
   bool mSentRedirect1BeginFailed    : 1;
   bool mReceivedRedirect2Verify     : 1;
 
+  nsRefPtr<OfflineObserver> mObserver;
+
   PBOverrideStatus mPBOverride;
 
   nsCOMPtr<nsILoadContext> mLoadContext;
   nsRefPtr<nsHttpHandler>  mHttpHandler;
 
   nsRefPtr<HttpChannelParentListener> mParentListener;
   // Set to the canceled status value if the main channel was canceled.
   nsresult mStatus;
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -2579,18 +2579,26 @@ nsHttpChannel::OpenCacheEntry(bool using
         // This is a fallback channel, open fallback URI instead
         rv = NS_NewURI(getter_AddRefs(openURI), mFallbackKey);
         NS_ENSURE_SUCCESS(rv, rv);
     }
     else {
         openURI = mURI;
     }
 
+    uint32_t appId = info->AppId();
+    bool appOffline = false;
+
+    if (appId != NECKO_NO_APP_ID) {
+        gIOService->IsAppOffline(appId, &appOffline);
+        LOG(("nsHttpChannel::OpenCacheEntry appId: %u, offline: %d\n", appId, appOffline));
+    }
+
     uint32_t cacheEntryOpenFlags;
-    bool offline = gIOService->IsOffline();
+    bool offline = gIOService->IsOffline() || appOffline;
     if (offline || (mLoadFlags & INHIBIT_CACHING)) {
         if (BYPASS_LOCAL_CACHE(mLoadFlags) && !offline) {
             goto bypassCacheEntryOpen;
         }
         cacheEntryOpenFlags = nsICacheStorage::OPEN_READONLY;
         mCacheEntryIsReadOnly = true;
     }
     else if (BYPASS_LOCAL_CACHE(mLoadFlags) && !mApplicationCache) {
--- a/netwerk/protocol/websocket/WebSocketChannelParent.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.cpp
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebSocketLog.h"
 #include "WebSocketChannelParent.h"
 #include "nsIAuthPromptProvider.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "SerializedLoadContext.h"
+#include "nsIOService.h"
+#include "mozilla/net/NeckoCommon.h"
 
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace net {
 
 NS_IMPL_ISUPPORTS(WebSocketChannelParent,
                   nsIWebSocketListener,
@@ -28,18 +30,25 @@ WebSocketChannelParent::WebSocketChannel
   , mIPCOpen(true)
 {
   // Websocket channels can't have a private browsing override
   MOZ_ASSERT_IF(!aLoadContext, aOverrideStatus == kPBOverride_Unset);
 #if defined(PR_LOGGING)
   if (!webSocketLog)
     webSocketLog = PR_NewLogModule("nsWebSocket");
 #endif
+  mObserver = new OfflineObserver(this);
 }
 
+WebSocketChannelParent::~WebSocketChannelParent()
+{
+  if (mObserver) {
+    mObserver->RemoveObserver();
+  }
+}
 //-----------------------------------------------------------------------------
 // WebSocketChannelParent::PWebSocketChannelParent
 //-----------------------------------------------------------------------------
 
 bool
 WebSocketChannelParent::RecvDeleteSelf()
 {
   LOG(("WebSocketChannelParent::RecvDeleteSelf() %p\n", this));
@@ -58,16 +67,27 @@ WebSocketChannelParent::RecvAsyncOpen(co
                                       const uint32_t& aPingTimeout,
                                       const bool& aClientSetPingTimeout)
 {
   LOG(("WebSocketChannelParent::RecvAsyncOpen() %p\n", this));
 
   nsresult rv;
   nsCOMPtr<nsIURI> uri;
 
+
+  bool appOffline = false;
+  uint32_t appId = GetAppId();
+  if (appId != NECKO_UNKNOWN_APP_ID &&
+      appId != NECKO_NO_APP_ID) {
+    gIOService->IsAppOffline(appId, &appOffline);
+    if (appOffline) {
+      goto fail;
+    }
+  }
+
   if (aSecure) {
     mChannel =
       do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv);
   } else {
     mChannel =
       do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv);
   }
   if (NS_FAILED(rv))
@@ -112,16 +132,17 @@ fail:
 bool
 WebSocketChannelParent::RecvClose(const uint16_t& code, const nsCString& reason)
 {
   LOG(("WebSocketChannelParent::RecvClose() %p\n", this));
   if (mChannel) {
     nsresult rv = mChannel->Close(code, reason);
     NS_ENSURE_SUCCESS(rv, true);
   }
+
   return true;
 }
 
 bool
 WebSocketChannelParent::RecvSendMsg(const nsCString& aMsg)
 {
   LOG(("WebSocketChannelParent::RecvSendMsg() %p\n", this));
   if (mChannel) {
@@ -253,11 +274,29 @@ WebSocketChannelParent::GetInterface(con
     NS_ADDREF(mLoadContext);
     *result = static_cast<nsILoadContext*>(mLoadContext);
     return NS_OK;
   }
 
   return QueryInterface(iid, result);
 }
 
+void
+WebSocketChannelParent::OfflineDisconnect()
+{
+  if (mChannel) {
+    mChannel->Close(nsIWebSocketChannel::CLOSE_GOING_AWAY,
+                    nsCString("App is offline"));
+  }
+}
+
+uint32_t
+WebSocketChannelParent::GetAppId()
+{
+  uint32_t appId = NECKO_UNKNOWN_APP_ID;
+  if (mLoadContext) {
+    mLoadContext->GetAppId(&appId);
+  }
+  return appId;
+}
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/websocket/WebSocketChannelParent.h
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.h
@@ -10,28 +10,29 @@
 #include "mozilla/net/PWebSocketParent.h"
 #include "mozilla/net/NeckoParent.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIWebSocketListener.h"
 #include "nsIWebSocketChannel.h"
 #include "nsILoadContext.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
+#include "OfflineObserver.h"
 
 class nsIAuthPromptProvider;
 
 namespace mozilla {
 namespace net {
 
 class WebSocketChannelParent : public PWebSocketParent,
                                public nsIWebSocketListener,
+                               public DisconnectableParent,
                                public nsIInterfaceRequestor
 {
-  ~WebSocketChannelParent() {}
-
+  ~WebSocketChannelParent();
  public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIWEBSOCKETLISTENER
   NS_DECL_NSIINTERFACEREQUESTOR
 
   WebSocketChannelParent(nsIAuthPromptProvider* aAuthProvider,
                          nsILoadContext* aLoadContext,
                          PBOverrideStatus aOverrideStatus);
@@ -49,16 +50,20 @@ class WebSocketChannelParent : public PW
   bool RecvSendMsg(const nsCString& aMsg) MOZ_OVERRIDE;
   bool RecvSendBinaryMsg(const nsCString& aMsg) MOZ_OVERRIDE;
   bool RecvSendBinaryStream(const InputStreamParams& aStream,
                             const uint32_t& aLength) MOZ_OVERRIDE;
   bool RecvDeleteSelf() MOZ_OVERRIDE;
 
   void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
+  void OfflineDisconnect() MOZ_OVERRIDE;
+  uint32_t GetAppId() MOZ_OVERRIDE;
+  nsRefPtr<OfflineObserver> mObserver;
+
   nsCOMPtr<nsIAuthPromptProvider> mAuthProvider;
   nsCOMPtr<nsIWebSocketChannel> mChannel;
   nsCOMPtr<nsILoadContext> mLoadContext;
   bool mIPCOpen;
 
 };
 
 } // namespace net