Bug 786419 - 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 201214 1ed271ffb59cabf5349ed5f08a5799aff60a34ab
parent 201213 e7236c73a81c1746d2137bd3060a5a1b18bd428e
child 201215 c9ffa291cfe3597c53b00a37d485c9d730586a89
push id48110
push userjduell@mozilla.com
push dateSat, 23 Aug 2014 07:15:33 +0000
treeherdermozilla-inbound@1ed271ffb59c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjduell
bugs786419
milestone34.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 786419 - Provide way to "set network offline" per app r=jduell
content/html/content/src/nsHTMLDNSPrefetch.cpp
dom/base/Navigator.cpp
dom/base/nsGlobalWindow.cpp
dom/ipc/PBrowser.ipdl
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
dom/media/PeerConnection.js
dom/network/src/TCPSocketParent.cpp
dom/network/src/TCPSocketParent.h
dom/network/src/UDPSocketParent.cpp
dom/network/src/UDPSocketParent.h
dom/workers/RuntimeService.cpp
dom/workers/WorkerPrivate.cpp
netwerk/base/public/nsIIOService.idl
netwerk/base/public/nsNetUtil.h
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/ftp/nsFtpConnectionThread.cpp
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
netwerk/test/unit_ipc/child_app_offline.js
netwerk/test/unit_ipc/test_app_offline_http.js
netwerk/test/unit_ipc/xpcshell.ini
--- a/content/html/content/src/nsHTMLDNSPrefetch.cpp
+++ b/content/html/content/src/nsHTMLDNSPrefetch.cpp
@@ -89,16 +89,20 @@ nsHTMLDNSPrefetch::Shutdown()
   NS_IF_RELEASE(sDNSListener);
   
   return NS_OK;
 }
 
 bool
 nsHTMLDNSPrefetch::IsAllowed (nsIDocument *aDocument)
 {
+  if (NS_IsAppOffline(aDocument->NodePrincipal())) {
+    return false;
+  }
+
   // There is no need to do prefetch on non UI scenarios such as XMLHttpRequest.
   return aDocument->IsDNSPrefetchAllowed() && aDocument->GetWindow();
 }
 
 nsresult
 nsHTMLDNSPrefetch::Prefetch(Link *aElement, uint16_t flags)
 {
   if (!(sInitialized && sPrefetches && sDNSService && sDNSListener))
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -588,16 +588,20 @@ Navigator::CookieEnabled()
   }
 
   return cookieEnabled;
 }
 
 bool
 Navigator::OnLine()
 {
+  if (mWindow && mWindow->GetDoc()) {
+    return !NS_IsAppOffline(mWindow->GetDoc()->NodePrincipal());
+  }
+
   return !NS_IsOffline();
 }
 
 NS_IMETHODIMP
 Navigator::GetBuildID(nsAString& aBuildID)
 {
   if (!nsContentUtils::IsCallerChrome()) {
     const nsAdoptingString& override =
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -10740,17 +10740,17 @@ nsGlobalWindow::GetInterface(JSContext* 
 }
 
 void
 nsGlobalWindow::FireOfflineStatusEvent()
 {
   if (!IsCurrentInnerWindow())
     return;
   nsAutoString name;
-  if (NS_IsOffline()) {
+  if (NS_IsOffline() || NS_IsAppOffline(GetPrincipal())) {
     name.AssignLiteral("offline");
   } else {
     name.AssignLiteral("online");
   }
   // The event is fired at the body element, or if there is no body element,
   // at the document.
   nsCOMPtr<EventTarget> eventTarget = mDoc.get();
   nsHTMLDocument* htmlDoc = mDoc->AsHTMLDocument();
@@ -11296,17 +11296,18 @@ nsGlobalWindow::UnregisterIdleObserver(n
 
   return NS_OK;
 }
 
 nsresult
 nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic,
                         const char16_t* aData)
 {
-  if (!nsCRT::strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) {
+  if (!nsCRT::strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC) ||
+      !nsCRT::strcmp(aTopic, NS_IOSERVICE_APP_OFFLINE_STATUS_TOPIC)) {
     if (IsFrozen()) {
       // if an even number of notifications arrive while we're frozen,
       // we don't need to fire.
       mFireOfflineStatusChangeEventOnThaw = !mFireOfflineStatusChangeEventOnThaw;
     } else {
       FireOfflineStatusEvent();
     }
     return NS_OK;
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -516,16 +516,21 @@ child:
     async RequestNotifyAfterRemotePaint();
 
     /**
      * Tell the child that the UI resolution changed for the containing
      * window.
      */
     UIResolutionChanged();
 
+    /**
+     * Tell the child of an app's offline status
+     */
+    AppOfflineStatus(uint32_t id, bool offline);
+
 /*
  * FIXME: write protocol!
 
 state LIVE:
     send LoadURL goto LIVE;
 //etc.
     send Destroy goto DYING;
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -75,16 +75,17 @@
 #include "nsViewportInfo.h"
 #include "JavaScriptChild.h"
 #include "nsILoadContext.h"
 #include "ipc/nsGUIEventIPC.h"
 #include "mozilla/gfx/Matrix.h"
 #include "UnitTransforms.h"
 #include "ClientLayerManager.h"
 #include "LayersLogging.h"
+#include "nsIOService.h"
 
 #include "nsColorPickerProxy.h"
 
 #define BROWSER_ELEMENT_CHILD_SCRIPT \
     NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js")
 
 #define TABC_LOG(...)
 // #define TABC_LOG(...) printf_stderr("TABC: " __VA_ARGS__)
@@ -2440,16 +2441,28 @@ TabChild::RecvAsyncMessage(const nsStrin
       static_cast<nsFrameMessageManager*>(mTabChildGlobal->mMessageManager.get());
     CpowIdHolder cpows(Manager()->GetCPOWManager(), aCpows);
     mm->ReceiveMessage(static_cast<EventTarget*>(mTabChildGlobal),
                        aMessage, false, &cloneData, &cpows, aPrincipal, nullptr);
   }
   return true;
 }
 
+bool
+TabChild::RecvAppOfflineStatus(const uint32_t& aId, const bool& aOffline)
+{
+  // Instantiate the service to make sure gIOService is initialized
+  nsCOMPtr<nsIIOService> ioService = mozilla::services::GetIOService();
+  if (gIOService && ioService) {
+    gIOService->SetAppOfflineInternal(aId, aOffline ?
+      nsIAppOfflineInfo::OFFLINE : nsIAppOfflineInfo::ONLINE);
+  }
+  return true;
+}
+
 class UnloadScriptEvent : public nsRunnable
 {
 public:
   UnloadScriptEvent(TabChild* aTabChild, TabChildGlobal* aTabChildGlobal)
     : mTabChild(aTabChild), mTabChildGlobal(aTabChildGlobal)
   { }
 
   NS_IMETHOD Run()
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -363,16 +363,18 @@ public:
     virtual bool RecvActivateFrameEvent(const nsString& aType, const bool& capture) MOZ_OVERRIDE;
     virtual bool RecvLoadRemoteScript(const nsString& aURL,
                                       const bool& aRunInGlobalScope) MOZ_OVERRIDE;
     virtual bool RecvAsyncMessage(const nsString& aMessage,
                                   const ClonedMessageData& aData,
                                   const InfallibleTArray<CpowEntry>& aCpows,
                                   const IPC::Principal& aPrincipal) MOZ_OVERRIDE;
 
+    virtual bool RecvAppOfflineStatus(const uint32_t& aId, const bool& aOffline) MOZ_OVERRIDE;
+
     virtual PDocumentRendererChild*
     AllocPDocumentRendererChild(const nsRect& documentRect, const gfx::Matrix& transform,
                                 const nsString& bgcolor,
                                 const uint32_t& renderFlags, const bool& flushLayout,
                                 const nsIntSize& renderSize) MOZ_OVERRIDE;
     virtual bool DeallocPDocumentRendererChild(PDocumentRendererChild* actor) MOZ_OVERRIDE;
     virtual bool RecvPDocumentRendererConstructor(PDocumentRendererChild* actor,
                                                   const nsRect& documentRect,
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -232,16 +232,17 @@ TabParent::TabParent(nsIContentParent* a
   , mDPI(0)
   , mDefaultScale(0)
   , mShown(false)
   , mUpdatedDimensions(false)
   , mManager(aManager)
   , mMarkedDestroying(false)
   , mIsDestroyed(false)
   , mAppPackageFileDescriptorSent(false)
+  , mSendOfflineStatus(true)
   , mChromeFlags(aChromeFlags)
 {
   MOZ_ASSERT(aManager);
 }
 
 TabParent::~TabParent()
 {
 }
@@ -497,16 +498,24 @@ TabParent::LoadURL(nsIURI* aURI)
 
     if (!mShown) {
         NS_WARNING(nsPrintfCString("TabParent::LoadURL(%s) called before "
                                    "Show(). Ignoring LoadURL.\n",
                                    spec.get()).get());
         return;
     }
 
+    uint32_t appId = OwnOrContainingAppId();
+    if (mSendOfflineStatus && NS_IsAppOffline(appId)) {
+      // If the app is offline in the parent process
+      // pass that state to the child process as well
+      unused << SendAppOfflineStatus(appId, true);
+    }
+    mSendOfflineStatus = false;
+
     unused << SendLoadURL(spec);
 
     // If this app is a packaged app then we can speed startup by sending over
     // the file descriptor for the "application.zip" file that it will
     // invariably request. Only do this once.
     if (!mAppPackageFileDescriptorSent) {
         mAppPackageFileDescriptorSent = true;
 
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -411,16 +411,20 @@ private:
     // managing PContent.
     bool mMarkedDestroying;
     // When true, the TabParent is invalid and we should not send IPC messages
     // anymore.
     bool mIsDestroyed;
     // Whether we have already sent a FileDescriptor for the app package.
     bool mAppPackageFileDescriptorSent;
 
+    // Whether we need to send the offline status to the TabChild
+    // This is true, until the first call of LoadURL
+    bool mSendOfflineStatus;
+
     uint32_t mChromeFlags;
 
     nsCOMPtr<nsILoadContext> mLoadContext;
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -145,16 +145,25 @@ GlobalPCList.prototype = {
     }
     else if (topic == "network:offline-status-changed") {
       if (data == "offline") {
         // this._list shold be empty here
         this._networkdown = true;
       } else if (data == "online") {
         this._networkdown = false;
       }
+    } else if (topic == "network:app-offline-status-changed") {
+      // App just went offline. The subject also contains the appId,
+      // but navigator.onLine checks that for us
+      if (!this._networkdown && !this._win.navigator.onLine) {
+        for (let winId in this._list) {
+          cleanupWinId(this._list, winId);
+        }
+      }
+      this._networkdown = !this._win.navigator.onLine;
     } else if (topic == "gmp-plugin-crash") {
       // a plugin crashed; if it's associated with any of our PCs, fire an
       // event to the DOM window
       let sep = data.indexOf(' ');
       let pluginId = data.slice(0, sep);
       let rest = data.slice(sep+1);
       // This presumes no spaces in the name!
       sep = rest.indexOf(' ');
@@ -326,17 +335,17 @@ RTCPeerConnection.prototype = {
     this._trickleIce = Services.prefs.getBoolPref("media.peerconnection.trickle_ice");
     if (!rtcConfig.iceServers ||
         !Services.prefs.getBoolPref("media.peerconnection.use_document_iceservers")) {
       rtcConfig.iceServers =
         JSON.parse(Services.prefs.getCharPref("media.peerconnection.default_iceservers"));
     }
     this._mustValidateRTCConfiguration(rtcConfig,
         "RTCPeerConnection constructor passed invalid RTCConfiguration");
-    if (_globalPCList._networkdown) {
+    if (_globalPCList._networkdown || !this._win.navigator.onLine) {
       throw new this._win.DOMError("",
           "Can't create RTCPeerConnections when the network is down");
     }
 
     this.makeGetterSetterEH("onaddstream");
     this.makeGetterSetterEH("onaddtrack");
     this.makeGetterSetterEH("onicecandidate");
     this.makeGetterSetterEH("onnegotiationneeded");
--- a/dom/network/src/TCPSocketParent.cpp
+++ b/dom/network/src/TCPSocketParent.cpp
@@ -45,20 +45,65 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPSock
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocketParentBase)
   NS_INTERFACE_MAP_ENTRY(nsITCPSocketParent)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 TCPSocketParentBase::TCPSocketParentBase()
 : mIPCOpen(false)
 {
+  mObserver = new mozilla::net::OfflineObserver(this);
 }
 
 TCPSocketParentBase::~TCPSocketParentBase()
 {
+  if (mObserver) {
+    mObserver->RemoveObserver();
+  }
+}
+
+uint32_t
+TCPSocketParent::GetAppId()
+{
+  uint32_t appId = nsIScriptSecurityManager::UNKNOWN_APP_ID;
+  const PContentParent *content = Manager()->Manager();
+  const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
+  if (browsers.Length() > 0) {
+    TabParent *tab = static_cast<TabParent*>(browsers[0]);
+    appId = tab->OwnAppId();
+  }
+  return appId;
+};
+
+nsresult
+TCPSocketParent::OfflineNotification(nsISupports *aSubject)
+{
+  nsCOMPtr<nsIAppOfflineInfo> info(do_QueryInterface(aSubject));
+  if (!info) {
+    return NS_OK;
+  }
+
+  uint32_t targetAppId = nsIScriptSecurityManager::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 (mSocket && NS_IsAppOffline(appId)) {
+    mSocket->Close();
+    mSocket = nullptr;
+    mIntermediaryObj = nullptr;
+    mIntermediary = nullptr;
+  }
+
+  return NS_OK;
 }
 
 void
 TCPSocketParentBase::ReleaseIPDLReference()
 {
   MOZ_ASSERT(mIPCOpen);
   mIPCOpen = false;
   this->Release();
@@ -90,22 +135,22 @@ TCPSocketParent::RecvOpen(const nsString
   // tests without this loophole.
   if (net::UsingNeckoIPCSecurity() &&
       !AssertAppProcessPermission(Manager()->Manager(), "tcp-socket")) {
     FireInteralError(this, __LINE__);
     return true;
   }
 
   // Obtain App ID
-  uint32_t appId = nsIScriptSecurityManager::NO_APP_ID;
-  const PContentParent *content = Manager()->Manager();
-  const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
-  if (browsers.Length() > 0) {
-    TabParent *tab = static_cast<TabParent*>(browsers[0]);
-    appId = tab->OwnAppId();
+  uint32_t appId = GetAppId();
+
+  if (NS_IsAppOffline(appId)) {
+    NS_ERROR("Can't open socket because app is offline");
+    FireInteralError(this, __LINE__);
+    return true;
   }
 
   nsresult rv;
   mIntermediary = do_CreateInstance("@mozilla.org/tcp-socket-intermediary;1", &rv);
   if (NS_FAILED(rv)) {
     FireInteralError(this, __LINE__);
     return true;
   }
--- a/dom/network/src/TCPSocketParent.h
+++ b/dom/network/src/TCPSocketParent.h
@@ -6,40 +6,43 @@
 #define mozilla_dom_TCPSocketParent_h
 
 #include "mozilla/net/PTCPSocketParent.h"
 #include "nsITCPSocketParent.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCOMPtr.h"
 #include "nsIDOMTCPSocket.h"
 #include "js/TypeDecls.h"
+#include "mozilla/net/OfflineObserver.h"
 
 #define TCPSOCKETPARENT_CID \
   { 0x4e7246c6, 0xa8b3, 0x426d, { 0x9c, 0x17, 0x76, 0xda, 0xb1, 0xe1, 0xe1, 0x4a } }
 
 namespace mozilla {
 namespace dom {
 
 class PBrowserParent;
 
 class TCPSocketParentBase : public nsITCPSocketParent
+                          , public mozilla::net::DisconnectableParent
 {
 public:
   NS_DECL_CYCLE_COLLECTION_CLASS(TCPSocketParentBase)
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 
   void AddIPDLReference();
   void ReleaseIPDLReference();
 
 protected:
   TCPSocketParentBase();
   virtual ~TCPSocketParentBase();
 
   nsCOMPtr<nsITCPSocketIntermediary> mIntermediary;
   nsCOMPtr<nsIDOMTCPSocket> mSocket;
+  nsRefPtr<mozilla::net::OfflineObserver> mObserver;
   bool mIPCOpen;
 };
 
 class TCPSocketParent : public mozilla::net::PTCPSocketParent
                       , public TCPSocketParentBase
 {
 public:
   NS_DECL_NSITCPSOCKETPARENT
@@ -53,16 +56,18 @@ public:
   virtual bool RecvStartTLS() MOZ_OVERRIDE;
   virtual bool RecvSuspend() MOZ_OVERRIDE;
   virtual bool RecvResume() MOZ_OVERRIDE;
   virtual bool RecvClose() MOZ_OVERRIDE;
   virtual bool RecvData(const SendableData& aData,
                         const uint32_t& aTrackingNumber) MOZ_OVERRIDE;
   virtual bool RecvRequestDelete() MOZ_OVERRIDE;
 
+  virtual nsresult OfflineNotification(nsISupports *) MOZ_OVERRIDE;
+  virtual uint32_t GetAppId() MOZ_OVERRIDE;
 private:
   virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
   JSObject* mIntermediaryObj;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/network/src/UDPSocketParent.cpp
+++ b/dom/network/src/UDPSocketParent.cpp
@@ -6,16 +6,20 @@
 
 #include "nsIServiceManager.h"
 #include "UDPSocketParent.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIUDPSocket.h"
 #include "nsINetAddr.h"
 #include "mozilla/unused.h"
 #include "mozilla/net/DNS.h"
+#include "nsNetUtil.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/net/PNeckoParent.h"
 
 namespace mozilla {
 namespace dom {
 
 static void
 FireInternalError(mozilla::net::PUDPSocketParent *aActor, uint32_t aLineNo)
 {
   mozilla::unused <<
@@ -53,20 +57,70 @@ ConvertNetAddrToString(mozilla::net::Net
   NetAddrToString(&netAddr, address->BeginWriting(), bufSize);
   address->SetLength(strlen(address->BeginReading()));
 
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(UDPSocketParent, nsIUDPSocketListener)
 
+UDPSocketParent::UDPSocketParent(nsIUDPSocketFilter *filter)
+  : mIPCOpen(true)
+  , mFilter(filter)
+{
+  mObserver = new mozilla::net::OfflineObserver(this);
+}
+
 UDPSocketParent::~UDPSocketParent()
 {
+  if (mObserver) {
+    mObserver->RemoveObserver();
+  }
 }
 
+uint32_t
+UDPSocketParent::GetAppId()
+{
+  uint32_t appId = nsIScriptSecurityManager::UNKNOWN_APP_ID;
+  const PContentParent *content = Manager()->Manager();
+  const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
+  if (browsers.Length() > 0) {
+    TabParent *tab = static_cast<TabParent*>(browsers[0]);
+    appId = tab->OwnAppId();
+  }
+  return appId;
+};
+
+nsresult
+UDPSocketParent::OfflineNotification(nsISupports *aSubject)
+{
+  nsCOMPtr<nsIAppOfflineInfo> info(do_QueryInterface(aSubject));
+  if (!info) {
+    return NS_OK;
+  }
+
+  uint32_t targetAppId = nsIScriptSecurityManager::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 (mSocket && NS_IsAppOffline(appId)) {
+    mSocket->Close();
+  }
+
+  return NS_OK;
+}
+
+
 // PUDPSocketParent methods
 
 bool
 UDPSocketParent::Init(const nsCString &aHost, const uint16_t aPort)
 {
   nsresult rv;
   NS_ASSERTION(mFilter, "No packet filter");
 
--- a/dom/network/src/UDPSocketParent.h
+++ b/dom/network/src/UDPSocketParent.h
@@ -6,47 +6,50 @@
 
 #ifndef mozilla_dom_UDPSocketParent_h__
 #define mozilla_dom_UDPSocketParent_h__
 
 #include "mozilla/net/PUDPSocketParent.h"
 #include "nsCOMPtr.h"
 #include "nsIUDPSocket.h"
 #include "nsIUDPSocketFilter.h"
+#include "mozilla/net/OfflineObserver.h"
 
 namespace mozilla {
 namespace dom {
 
 class UDPSocketParent : public mozilla::net::PUDPSocketParent
                       , public nsIUDPSocketListener
+                      , public mozilla::net::DisconnectableParent
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIUDPSOCKETLISTENER
 
-  explicit UDPSocketParent(nsIUDPSocketFilter* filter) :
-    mIPCOpen(true),
-    mFilter(filter) {}
+  explicit UDPSocketParent(nsIUDPSocketFilter* filter);
 
   bool Init(const nsCString& aHost, const uint16_t aPort);
 
   virtual bool RecvClose() MOZ_OVERRIDE;
   virtual bool RecvData(const InfallibleTArray<uint8_t>& aData,
                         const nsCString& aRemoteAddress,
                         const uint16_t& aPort) MOZ_OVERRIDE;
   virtual bool RecvDataWithAddress( const InfallibleTArray<uint8_t>& data,
                                     const mozilla::net::NetAddr& addr);
   virtual bool RecvRequestDelete() MOZ_OVERRIDE;
 
+  virtual nsresult OfflineNotification(nsISupports *) MOZ_OVERRIDE;
+  virtual uint32_t GetAppId() MOZ_OVERRIDE;
 private:
   virtual ~UDPSocketParent();
 
   virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
   bool mIPCOpen;
   nsCOMPtr<nsIUDPSocket> mSocket;
   nsCOMPtr<nsIUDPSocketFilter> mFilter;
+  nsRefPtr<mozilla::net::OfflineObserver> mObserver;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // !defined(mozilla_dom_UDPSocketParent_h__)
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -2471,16 +2471,38 @@ RuntimeService::Observe(nsISupports* aSu
     GarbageCollectAllWorkers(/* shrinking = */ true);
     CycleCollectAllWorkers();
     return NS_OK;
   }
   if (!strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) {
     SendOfflineStatusChangeEventToAllWorkers(NS_IsOffline());
     return NS_OK;
   }
+  if (!strcmp(aTopic, NS_IOSERVICE_APP_OFFLINE_STATUS_TOPIC)) {
+    nsCOMPtr<nsIAppOfflineInfo> info(do_QueryInterface(aSubject));
+    if (!info) {
+      return NS_OK;
+    }
+    nsIPrincipal * principal = GetPrincipalForAsmJSCacheOp();
+    if (!principal) {
+      return NS_OK;
+    }
+
+    uint32_t appId = nsIScriptSecurityManager::UNKNOWN_APP_ID;
+    principal->GetAppId(&appId);
+
+    uint32_t notificationAppId = nsIScriptSecurityManager::UNKNOWN_APP_ID;
+    info->GetAppId(&notificationAppId);
+
+    if (appId != notificationAppId) {
+      return NS_OK;
+    }
+
+    SendOfflineStatusChangeEventToAllWorkers(NS_IsAppOffline(appId));
+  }
 
   NS_NOTREACHED("Unknown observer topic!");
   return NS_OK;
 }
 
 /* static */ void
 RuntimeService::WorkerPrefChanged(const char* aPrefName, void* aClosure)
 {
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -3523,17 +3523,17 @@ WorkerPrivate::WorkerPrivate(JSContext* 
   if (aParent) {
     aParent->AssertIsOnWorkerThread();
     aParent->GetAllPreferences(mPreferences);
     mOnLine = aParent->OnLine();
   }
   else {
     AssertIsOnMainThread();
     RuntimeService::GetDefaultPreferences(mPreferences);
-    mOnLine = !NS_IsOffline();
+    mOnLine = !NS_IsOffline() && !NS_IsAppOffline(aLoadInfo.mPrincipal);
   }
 }
 
 WorkerPrivate::~WorkerPrivate()
 {
 }
 
 // static
--- 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"
 %}
--- a/netwerk/base/public/nsNetUtil.h
+++ b/netwerk/base/public/nsNetUtil.h
@@ -70,16 +70,17 @@
 #include "nsIIDNService.h"
 #include "nsIChannelEventSink.h"
 #include "nsIChannelPolicy.h"
 #include "nsISocketProviderService.h"
 #include "nsISocketProvider.h"
 #include "nsIRedirectChannelRegistrar.h"
 #include "nsIMIMEHeaderParam.h"
 #include "nsILoadContext.h"
+#include "nsIScriptSecurityManager.h"
 #include "mozilla/Services.h"
 #include "nsIPrivateBrowsingChannel.h"
 #include "mozIApplicationClearPrivateDataParams.h"
 #include "nsIOfflineCacheUpdate.h"
 #include "nsIContentSniffer.h"
 #include "nsCategoryCache.h"
 #include "nsStringStream.h"
 #include "nsIViewSourceChannel.h"
@@ -1611,16 +1612,40 @@ NS_IsOffline()
 {
     bool offline = true;
     nsCOMPtr<nsIIOService> ios = do_GetIOService();
     if (ios)
         ios->GetOffline(&offline);
     return offline;
 }
 
+inline bool
+NS_IsAppOffline(uint32_t appId)
+{
+    bool appOffline = false;
+    nsCOMPtr<nsIIOService> io(
+        do_GetService("@mozilla.org/network/io-service;1"));
+    if (io) {
+        io->IsAppOffline(appId, &appOffline);
+    }
+    return appOffline;
+}
+
+inline bool
+NS_IsAppOffline(nsIPrincipal * principal)
+{
+    if (!principal) {
+        return NS_IsOffline();
+    }
+    uint32_t appId = nsIScriptSecurityManager::UNKNOWN_APP_ID;
+    principal->GetAppId(&appId);
+
+    return NS_IsAppOffline(appId);
+}
+
 /**
  * Helper functions for implementing nsINestedURI::innermostURI.
  *
  * Note that NS_DoImplGetInnermostURI is "private" -- call
  * NS_ImplGetInnermostURI instead.
  */
 inline nsresult
 NS_DoImplGetInnermostURI(nsINestedURI* nestedURI, nsIURI** result)
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/OfflineObserver.cpp
@@ -0,0 +1,118 @@
+/* -*- 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;
+}
+
+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() = 0;
+
+  // 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
@@ -11,16 +11,17 @@ EXPORTS += [
     'nsURLHelper.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',
@@ -78,16 +79,17 @@ UNIFIED_SOURCES += [
     'Tickler.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,31 +23,39 @@
 #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 "nsThreadUtils.h"
+#include "mozilla/net/NeckoCommon.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkManager.h"
+#endif
 
 #if defined(XP_WIN)
 #include "nsNativeConnectionHelper.h"
 #endif
 
 using namespace mozilla;
+using mozilla::net::IsNeckoChild;
 
 #define PORT_PREF_PREFIX           "network.security.ports."
 #define PORT_PREF(x)               PORT_PREF_PREFIX x
 #define AUTODIAL_PREF              "network.autodial-helper.enabled"
 #define MANAGE_OFFLINE_STATUS_PREF "network.manage-offline-status"
 
 // Nb: these have been misnomers since bug 715770 removed the buffer cache.
 // "network.segment.count" and "network.segment.size" would be better names,
@@ -126,33 +134,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)
+    , mPreviousWifiState(-1)
 {
 }
 
 nsresult
 nsIOService::Init()
 {
     nsresult rv;
 
@@ -194,16 +207,17 @@ nsIOService::Init()
     nsCOMPtr<nsIObserverService> observerService =
         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, kNetworkActiveChanged, true);
     }
     else
         NS_WARNING("failed to get observer service");
 
     gIOService = this;
 
     InitializeNetworkLinkService();
 
@@ -896,16 +910,72 @@ 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.
+// Always returns false in the child process (should not depend on this method)
+static bool
+IsWifiActive()
+{
+    // We don't need to do this check inside the child process
+    if (IsNeckoChild()) {
+        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);
@@ -951,17 +1021,47 @@ nsIOService::Observe(nsISupports *subjec
         // Break circular reference.
         mProxyService = nullptr;
     }
     else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
         if (!mOfflineForProfileChange && mManageOfflineStatus) {
             TrackNetworkLinkStatusForOffline();
         }
     }
-    
+    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,
                               nsACString &aCharset,
                               bool *aHadCharset,
@@ -1245,8 +1345,161 @@ 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(!IsNeckoChild(),
+                   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;
+}
+
+// This method may be called in both the parent and the child process
+// In parent it only gets called in from nsIOService::SetAppOffline
+// and SetAppOfflineMainThread::Run
+// In the child, it may get called from NeckoChild::RecvAppOfflineStatus
+// and TabChild::RecvAppOfflineStatus.
+// Note that in the child process, apps should never be in a WIFI_ONLY
+// because wifi status is not available on the child
+void
+nsIOService::SetAppOfflineInternal(uint32_t aAppId, int32_t aState)
+{
+    MOZ_ASSERT(NS_IsMainThread());
+    NS_ENSURE_TRUE_VOID(NS_IsMainThread());
+
+    int32_t state = nsIAppOfflineInfo::ONLINE;
+    mAppsOfflineStatus.Get(aAppId, &state);
+    if (state == aState) {
+        // The app is already in this state. Nothing needs to be done.
+        return;
+    }
+
+    // wifiActive will always be false in the child process
+    // but it will be true in the parent process on Desktop Firefox as it does
+    // not have wifi-detection capabilities
+    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:
+        MOZ_RELEASE_ASSERT(!IsNeckoChild());
+        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:
+            MOZ_RELEASE_ASSERT(!IsNeckoChild());
+            *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 TrackNetworkLinkStatusForOffline();
@@ -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;
@@ -124,20 +139,58 @@ private:
     nsWeakPtr                            mWeakHandler[NS_N(gScheme)];
 
     // cached categories
     nsCategoryCache<nsIChannelEventSink> mChannelEventSinks;
 
     nsTArray<int32_t>                    mRestrictedPortList;
 
     bool                                 mAutoDialEnabled;
+
+    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 {
 
@@ -314,10 +315,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
@@ -72,16 +72,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 "nsIUDPSocketFilter.h"
 #include "nsEscape.h"
 #include "RemoteOpenFileParent.h"
 #include "SerializedLoadContext.h"
 #include "nsAuthInformationHolder.h"
 #include "nsIAuthPromptCallback.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 :
@@ -800,9 +807,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,18 @@ 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 uint32_t GetAppId() MOZ_OVERRIDE { return NECKO_UNKNOWN_APP_ID; }
   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);
@@ -201,14 +204,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
@@ -11,16 +11,17 @@
 #include "nsFtpProtocolHandler.h"
 #include "nsIEncodedChannel.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIForcePendingChannel.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/unused.h"
 #include "SerializedLoadContext.h"
+#include "nsIOService.h"
 
 using namespace mozilla::ipc;
 
 #undef LOG
 #define LOG(args) PR_LOG(gFTPLog, PR_LOG_DEBUG, args)
 
 namespace mozilla {
 namespace net {
@@ -32,21 +33,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;
@@ -103,17 +109,28 @@ 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));
+  }
+
   nsresult rv;
+  if (app_offline)
+    return SendFailedAsyncOpen(NS_ERROR_OFFLINE);
+
   nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
   if (NS_FAILED(rv))
     return SendFailedAsyncOpen(rv);
 
   nsCOMPtr<nsIChannel> chan;
   rv = NS_NewChannel(getter_AddRefs(chan), uri, ios);
   if (NS_FAILED(rv))
     return SendFailedAsyncOpen(rv);
@@ -168,16 +185,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();
@@ -614,16 +632,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
@@ -74,16 +76,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;
@@ -98,14 +103,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/ftp/nsFtpConnectionThread.cpp
+++ b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp
@@ -2508,17 +2508,21 @@ nsFtpState::CheckCache()
                          getter_AddRefs(session));
     if (!session)
         return false;
     session->SetDoomEntriesIfExpired(false);
     session->SetIsPrivate(isPrivate);
 
     // Set cache access requested:
     nsCacheAccessMode accessReq;
-    if (NS_IsOffline()) {
+    uint32_t appId;
+    bool isInBrowser;
+    NS_GetAppInfo(mChannel, &appId, &isInBrowser);
+
+    if (NS_IsOffline() || NS_IsAppOffline(appId)) {
         accessReq = nsICache::ACCESS_READ; // can only read
     } else if (mChannel->HasLoadFlag(nsIRequest::LOAD_BYPASS_CACHE)) {
         accessReq = nsICache::ACCESS_WRITE; // replace cache entry
     } else {
         accessReq = nsICache::ACCESS_READ_WRITE; // normal browsing
     }
 
     // Check to see if we are not allowed to write to the cache:
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -22,16 +22,18 @@
 #include "nsISerializable.h"
 #include "nsIAssociatedContentSecurity.h"
 #include "nsIApplicationCacheService.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "SerializedLoadContext.h"
 #include "nsIAuthInformation.h"
 #include "nsIAuthPromptCallback.h"
+#include "nsIOService.h"
+#include "nsICachingChannel.h"
 
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace net {
 
 HttpChannelParent::HttpChannelParent(const PBrowserOrId& iframeEmbedding,
@@ -59,20 +61,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.
@@ -155,17 +162,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,
@@ -195,16 +202,30 @@ HttpChannelParent::DoAsyncOpen(  const U
        this, uriSpec.get()));
 
   nsresult rv;
 
   nsCOMPtr<nsIIOService> ios(do_GetIOService(&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;
+    loadFlags |= nsIRequest::LOAD_FROM_CACHE;
+    loadFlags |= nsICachingChannel::LOAD_NO_NETWORK_IO;
+  }
+
   nsCOMPtr<nsIChannel> channel;
   rv = NS_NewChannel(getter_AddRefs(channel), uri, ios, nullptr, nullptr, loadFlags);
   if (NS_FAILED(rv))
     return SendFailedAsyncOpen(rv);
 
   mChannel = static_cast<nsHttpChannel *>(channel.get());
   if (mPBOverride != kPBOverride_Unset) {
     mChannel->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
@@ -280,20 +301,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));
@@ -328,16 +347,32 @@ 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) {
+    uint32_t loadFlags;
+    mChannel->GetLoadFlags(&loadFlags);
+    loadFlags |= nsICachingChannel::LOAD_ONLY_FROM_CACHE;
+    loadFlags |= nsIRequest::LOAD_FROM_CACHE;
+    loadFlags |= nsICachingChannel::LOAD_NO_NETWORK_IO;
+    mChannel->SetLoadFlags(loadFlags);
+  }
+
   return true;
 }
 
 bool
 HttpChannelParent::RecvSetPriority(const uint16_t& priority)
 {
   if (mChannel) {
     mChannel->SetPriority(priority);
@@ -996,16 +1031,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
@@ -121,16 +124,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;
@@ -142,16 +148,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
@@ -2556,18 +2556,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
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit_ipc/child_app_offline.js
@@ -0,0 +1,55 @@
+
+function inChildProcess() {
+  return Cc["@mozilla.org/xre/app-info;1"]
+           .getService(Ci.nsIXULRuntime)
+           .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+function makeChan(url, appId, inBrowser) {
+  var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+  var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel);
+  chan.notificationCallbacks = {
+    appId: appId,
+    isInBrowserElement: inBrowser,
+    QueryInterface: function(iid) {
+      if (iid.equals(Ci.nsILoadContext))
+        return this;
+      throw Cr.NS_ERROR_NO_INTERFACE;
+    },
+    getInterface: function(iid) { return this.QueryInterface(iid); }
+  };
+  return chan;
+}
+
+// Simple online load
+function run_test() {
+  do_test_pending();
+  var chan = makeChan("http://localhost:12345/first", 14, false);
+  chan.asyncOpen(new ChannelListener(checkResponse, "response0"), null);
+}
+
+// Should return cached result
+function test1() {
+  do_test_pending();
+  var chan = makeChan("http://localhost:12345/first", 14, false);
+  chan.asyncOpen(new ChannelListener(checkResponse, "response0"), null);
+}
+
+// This request should fail
+function test2() {
+  do_test_pending();
+  var chan = makeChan("http://localhost:12345/second", 14, false);
+  chan.asyncOpen(new ChannelListener(checkResponse, "", CL_EXPECT_FAILURE), null);
+}
+
+// This request should succeed
+function test3() {
+  do_test_pending();
+  var chan = makeChan("http://localhost:12345/second", 14, false);
+  chan.asyncOpen(new ChannelListener(checkResponse, "response3"), null);
+}
+
+function checkResponse(req, buffer, expected) {
+  do_check_eq(buffer, expected);
+  do_test_finished();
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_app_offline_http.js
@@ -0,0 +1,74 @@
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var test_index = 0;
+
+var responses = [
+  "response0", // This should be the first returned value
+  "response1", // This response should not be recevied. Load response0 from cache
+  "response2", // This request should fail
+  "response3", // This request should succeed
+  ];
+
+function http_handler(metadata, response) {
+  response.setHeader("Content-Type", "text/plain", false);
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setStatusLine(metadata.httpVersion, 200, "OK");
+  var body = responses[test_index];
+  response.bodyOutputStream.write(body, body.length);
+}
+
+
+function set_app_offline(appId, offline) {
+  let ioservice = Cc['@mozilla.org/network/io-service;1'].
+    getService(Ci.nsIIOService);
+
+  ioservice.setAppOffline(appId, offline);
+}
+
+var httpserv;
+
+function setup() {
+  httpserv = new HttpServer();
+  httpserv.registerPathHandler("/first", http_handler);
+  httpserv.registerPathHandler("/second", http_handler);
+  httpserv.start(12345);
+}
+
+function run_test() {
+  setup();
+  test0();
+}
+
+// Test that app 14 can open channel
+function test0() {
+  test_index = 0;
+  run_test_in_child("child_app_offline.js", test1);
+}
+
+// Set app 14 offline and check that it still gets a cached response
+function test1() {
+  test_index = 1;
+  set_app_offline(14, Ci.nsIAppOfflineInfo.OFFLINE);
+  sendCommand('test1();\n', test2);
+}
+
+// Check that app 14 can't open a channel to a new location
+function test2() {
+  test_index = 2;
+  sendCommand('test2();\n', test3);
+}
+
+
+// Set app online and check that it now works
+function test3() {
+  test_index = 3;
+  set_app_offline(14, Ci.nsIAppOfflineInfo.ONLINE);
+  sendCommand('test3();\n', ending);
+}
+
+function ending(val) {
+  do_test_finished();
+}
--- a/netwerk/test/unit_ipc/xpcshell.ini
+++ b/netwerk/test/unit_ipc/xpcshell.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 head = head_channels_clone.js head_cc.js
 tail =
 support-files = disabled_test_bug528292_wrap.js
+	child_app_offline.js
 
 [test_bug248970_cookie_wrap.js]
 [test_cacheflags_wrap.js]
 [test_cache_jar_wrap.js]
 [test_channel_close_wrap.js]
 [test_cookie_header_wrap.js]
 [test_cookiejars_wrap.js]
 [test_dns_service_wrap.js]
@@ -29,8 +30,9 @@ skip-if = true
 [test_redirect_different-protocol_wrap.js]
 [test_reentrancy_wrap.js]
 [test_resumable_channel_wrap.js]
 [test_simple_wrap.js]
 [test_xmlhttprequest_wrap.js]
 [test_XHR_redirects.js]
 [test_redirect_history_wrap.js]
 [test_reply_without_content_type_wrap.js]
+[test_app_offline_http.js]