From 077e9036e00ab5233ea13dcb4e20c174a18f9bc8 Mon Sep 17 00:00:00 2001 From: Valentin Gosu Date: Sat, 23 Aug 2014 06:05:56 +0300 Subject: [PATCH] Bug 786419 - Provide way to "set network offline" per app r=jduell --- .../html/content/src/nsHTMLDNSPrefetch.cpp | 4 + dom/base/Navigator.cpp | 4 + dom/base/nsGlobalWindow.cpp | 5 +- dom/ipc/PBrowser.ipdl | 5 + dom/ipc/TabChild.cpp | 13 + dom/ipc/TabChild.h | 2 + dom/ipc/TabParent.cpp | 9 + dom/ipc/TabParent.h | 4 + dom/media/PeerConnection.js | 11 +- dom/network/src/TCPSocketParent.cpp | 57 +++- dom/network/src/TCPSocketParent.h | 5 + dom/network/src/UDPSocketParent.cpp | 54 ++++ dom/network/src/UDPSocketParent.h | 9 +- dom/workers/RuntimeService.cpp | 22 ++ dom/workers/WorkerPrivate.cpp | 2 +- netwerk/base/public/nsIIOService.idl | 36 +++ netwerk/base/public/nsNetUtil.h | 25 ++ netwerk/base/src/OfflineObserver.cpp | 118 ++++++++ netwerk/base/src/OfflineObserver.h | 73 +++++ netwerk/base/src/moz.build | 2 + netwerk/base/src/nsIOService.cpp | 255 +++++++++++++++++- netwerk/base/src/nsIOService.h | 53 ++++ netwerk/ipc/NeckoChild.cpp | 13 + netwerk/ipc/NeckoChild.h | 1 + netwerk/ipc/NeckoParent.cpp | 46 ++++ netwerk/ipc/NeckoParent.h | 10 +- netwerk/ipc/PNecko.ipdl | 2 + netwerk/protocol/ftp/FTPChannelParent.cpp | 37 +++ netwerk/protocol/ftp/FTPChannelParent.h | 6 + .../protocol/ftp/nsFtpConnectionThread.cpp | 6 +- netwerk/protocol/http/HttpChannelParent.cpp | 60 ++++- netwerk/protocol/http/HttpChannelParent.h | 8 + netwerk/protocol/http/nsHttpChannel.cpp | 10 +- .../websocket/WebSocketChannelParent.cpp | 39 +++ .../websocket/WebSocketChannelParent.h | 9 +- netwerk/test/unit_ipc/child_app_offline.js | 55 ++++ .../test/unit_ipc/test_app_offline_http.js | 74 +++++ netwerk/test/unit_ipc/xpcshell.ini | 2 + 38 files changed, 1122 insertions(+), 24 deletions(-) create mode 100644 netwerk/base/src/OfflineObserver.cpp create mode 100644 netwerk/base/src/OfflineObserver.h create mode 100644 netwerk/test/unit_ipc/child_app_offline.js create mode 100644 netwerk/test/unit_ipc/test_app_offline_http.js diff --git a/content/html/content/src/nsHTMLDNSPrefetch.cpp b/content/html/content/src/nsHTMLDNSPrefetch.cpp index a9dc8c15bc4f..711795ed5be1 100644 --- a/content/html/content/src/nsHTMLDNSPrefetch.cpp +++ b/content/html/content/src/nsHTMLDNSPrefetch.cpp @@ -94,6 +94,10 @@ nsHTMLDNSPrefetch::Shutdown() 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(); } diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index b8aa1166903c..3f47b1b6a369 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -593,6 +593,10 @@ Navigator::CookieEnabled() bool Navigator::OnLine() { + if (mWindow && mWindow->GetDoc()) { + return !NS_IsAppOffline(mWindow->GetDoc()->NodePrincipal()); + } + return !NS_IsOffline(); } diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 5dbddb05d35e..66258f76f035 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -10745,7 +10745,7 @@ nsGlobalWindow::FireOfflineStatusEvent() if (!IsCurrentInnerWindow()) return; nsAutoString name; - if (NS_IsOffline()) { + if (NS_IsOffline() || NS_IsAppOffline(GetPrincipal())) { name.AssignLiteral("offline"); } else { name.AssignLiteral("online"); @@ -11301,7 +11301,8 @@ 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. diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index fb24f9d89d26..f59b01681f88 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -521,6 +521,11 @@ child: */ UIResolutionChanged(); + /** + * Tell the child of an app's offline status + */ + AppOfflineStatus(uint32_t id, bool offline); + /* * FIXME: write protocol! diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index 948647b5e572..7a5b178e5775 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -80,6 +80,7 @@ #include "UnitTransforms.h" #include "ClientLayerManager.h" #include "LayersLogging.h" +#include "nsIOService.h" #include "nsColorPickerProxy.h" @@ -2445,6 +2446,18 @@ TabChild::RecvAsyncMessage(const nsString& aMessage, return true; } +bool +TabChild::RecvAppOfflineStatus(const uint32_t& aId, const bool& aOffline) +{ + // Instantiate the service to make sure gIOService is initialized + nsCOMPtr ioService = mozilla::services::GetIOService(); + if (gIOService && ioService) { + gIOService->SetAppOfflineInternal(aId, aOffline ? + nsIAppOfflineInfo::OFFLINE : nsIAppOfflineInfo::ONLINE); + } + return true; +} + class UnloadScriptEvent : public nsRunnable { public: diff --git a/dom/ipc/TabChild.h b/dom/ipc/TabChild.h index 8b2d800816af..1c2f9676e493 100644 --- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -368,6 +368,8 @@ public: const InfallibleTArray& 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, diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index d305a1717790..c9476c220481 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -237,6 +237,7 @@ TabParent::TabParent(nsIContentParent* aManager, const TabContext& aContext, uin , mMarkedDestroying(false) , mIsDestroyed(false) , mAppPackageFileDescriptorSent(false) + , mSendOfflineStatus(true) , mChromeFlags(aChromeFlags) { MOZ_ASSERT(aManager); @@ -502,6 +503,14 @@ TabParent::LoadURL(nsIURI* aURI) 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 diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h index 1ee0c3002b95..3c608f84e812 100644 --- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -416,6 +416,10 @@ private: // 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 mLoadContext; diff --git a/dom/media/PeerConnection.js b/dom/media/PeerConnection.js index 9dc6f3105b4d..0f5728bc8b40 100644 --- a/dom/media/PeerConnection.js +++ b/dom/media/PeerConnection.js @@ -150,6 +150,15 @@ GlobalPCList.prototype = { } 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 @@ -331,7 +340,7 @@ RTCPeerConnection.prototype = { } 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"); } diff --git a/dom/network/src/TCPSocketParent.cpp b/dom/network/src/TCPSocketParent.cpp index e9de185ce137..c124673dc1f0 100644 --- a/dom/network/src/TCPSocketParent.cpp +++ b/dom/network/src/TCPSocketParent.cpp @@ -50,10 +50,55 @@ 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& browsers = content->ManagedPBrowserParent(); + if (browsers.Length() > 0) { + TabParent *tab = static_cast(browsers[0]); + appId = tab->OwnAppId(); + } + return appId; +}; + +nsresult +TCPSocketParent::OfflineNotification(nsISupports *aSubject) +{ + nsCOMPtr 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 @@ -95,12 +140,12 @@ TCPSocketParent::RecvOpen(const nsString& aHost, const uint16_t& aPort, const bo } // Obtain App ID - uint32_t appId = nsIScriptSecurityManager::NO_APP_ID; - const PContentParent *content = Manager()->Manager(); - const InfallibleTArray& browsers = content->ManagedPBrowserParent(); - if (browsers.Length() > 0) { - TabParent *tab = static_cast(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; diff --git a/dom/network/src/TCPSocketParent.h b/dom/network/src/TCPSocketParent.h index 462aaa5a1c7d..d5b097948522 100644 --- a/dom/network/src/TCPSocketParent.h +++ b/dom/network/src/TCPSocketParent.h @@ -11,6 +11,7 @@ #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 } } @@ -21,6 +22,7 @@ namespace dom { class PBrowserParent; class TCPSocketParentBase : public nsITCPSocketParent + , public mozilla::net::DisconnectableParent { public: NS_DECL_CYCLE_COLLECTION_CLASS(TCPSocketParentBase) @@ -35,6 +37,7 @@ protected: nsCOMPtr mIntermediary; nsCOMPtr mSocket; + nsRefPtr mObserver; bool mIPCOpen; }; @@ -58,6 +61,8 @@ public: 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; diff --git a/dom/network/src/UDPSocketParent.cpp b/dom/network/src/UDPSocketParent.cpp index 36fbb1a4105f..ad89f9b91c4f 100644 --- a/dom/network/src/UDPSocketParent.cpp +++ b/dom/network/src/UDPSocketParent.cpp @@ -11,6 +11,10 @@ #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 { @@ -58,10 +62,60 @@ ConvertNetAddrToString(mozilla::net::NetAddr &netAddr, nsACString *address, uint 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& browsers = content->ManagedPBrowserParent(); + if (browsers.Length() > 0) { + TabParent *tab = static_cast(browsers[0]); + appId = tab->OwnAppId(); + } + return appId; +}; + +nsresult +UDPSocketParent::OfflineNotification(nsISupports *aSubject) +{ + nsCOMPtr 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 diff --git a/dom/network/src/UDPSocketParent.h b/dom/network/src/UDPSocketParent.h index 9f24ec259cae..ea81c9ed210e 100644 --- a/dom/network/src/UDPSocketParent.h +++ b/dom/network/src/UDPSocketParent.h @@ -11,20 +11,20 @@ #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); @@ -36,6 +36,8 @@ public: 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(); @@ -44,6 +46,7 @@ private: bool mIPCOpen; nsCOMPtr mSocket; nsCOMPtr mFilter; + nsRefPtr mObserver; }; } // namespace dom diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index 96f4a583d59f..eedfc57bfd22 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -2476,6 +2476,28 @@ RuntimeService::Observe(nsISupports* aSubject, const char* aTopic, SendOfflineStatusChangeEventToAllWorkers(NS_IsOffline()); return NS_OK; } + if (!strcmp(aTopic, NS_IOSERVICE_APP_OFFLINE_STATUS_TOPIC)) { + nsCOMPtr 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(¬ificationAppId); + + if (appId != notificationAppId) { + return NS_OK; + } + + SendOfflineStatusChangeEventToAllWorkers(NS_IsAppOffline(appId)); + } NS_NOTREACHED("Unknown observer topic!"); return NS_OK; diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index e35d0758e56c..8089c5103f0b 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -3528,7 +3528,7 @@ WorkerPrivate::WorkerPrivate(JSContext* aCx, else { AssertIsOnMainThread(); RuntimeService::GetDefaultPreferences(mPreferences); - mOnLine = !NS_IsOffline(); + mOnLine = !NS_IsOffline() && !NS_IsAppOffline(aLoadInfo.mPrincipal); } } diff --git a/netwerk/base/public/nsIIOService.idl b/netwerk/base/public/nsIIOService.idl index d383926e7129..c37d428cde25 100644 --- a/netwerk/base/public/nsIIOService.idl +++ b/netwerk/base/public/nsIIOService.idl @@ -88,6 +88,24 @@ interface nsIIOService : nsISupports */ 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 @@ -117,6 +135,18 @@ interface nsIIOService : nsISupports 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 @@ -136,4 +166,10 @@ interface nsIIOService : nsISupports #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" %} diff --git a/netwerk/base/public/nsNetUtil.h b/netwerk/base/public/nsNetUtil.h index c6c6dc6cb83e..fa1d4b72af96 100644 --- a/netwerk/base/public/nsNetUtil.h +++ b/netwerk/base/public/nsNetUtil.h @@ -75,6 +75,7 @@ #include "nsIRedirectChannelRegistrar.h" #include "nsIMIMEHeaderParam.h" #include "nsILoadContext.h" +#include "nsIScriptSecurityManager.h" #include "mozilla/Services.h" #include "nsIPrivateBrowsingChannel.h" #include "mozIApplicationClearPrivateDataParams.h" @@ -1616,6 +1617,30 @@ NS_IsOffline() return offline; } +inline bool +NS_IsAppOffline(uint32_t appId) +{ + bool appOffline = false; + nsCOMPtr 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. * diff --git a/netwerk/base/src/OfflineObserver.cpp b/netwerk/base/src/OfflineObserver.cpp new file mode 100644 index 000000000000..40b0395cbcc6 --- /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 event = + NS_NewRunnableMethod(this, &OfflineObserver::RegisterOfflineObserverMainThread); + NS_DispatchToMainThread(event); + } +} + +void +OfflineObserver::RemoveOfflineObserver() +{ + if (NS_IsMainThread()) { + RemoveOfflineObserverMainThread(); + } else { + nsRefPtr event = + NS_NewRunnableMethod(this, &OfflineObserver::RemoveOfflineObserverMainThread); + NS_DispatchToMainThread(event); + } +} + +void +OfflineObserver::RegisterOfflineObserverMainThread() +{ + nsCOMPtr 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 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 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 diff --git a/netwerk/base/src/OfflineObserver.h b/netwerk/base/src/OfflineObserver.h new file mode 100644 index 000000000000..865bc0daa20c --- /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 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__ diff --git a/netwerk/base/src/moz.build b/netwerk/base/src/moz.build index cb4c1d026651..14454d3ced4e 100644 --- a/netwerk/base/src/moz.build +++ b/netwerk/base/src/moz.build @@ -16,6 +16,7 @@ EXPORTS.mozilla.net += [ 'ChannelDiverterParent.h', 'Dashboard.h', 'DashboardTypes.h', + 'OfflineObserver.h', ] UNIFIED_SOURCES += [ @@ -83,6 +84,7 @@ SOURCES += [ 'nsAsyncRedirectVerifyHelper.cpp', 'nsSocketTransport2.cpp', 'nsSocketTransportService2.cpp', + 'OfflineObserver.cpp', ] if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': diff --git a/netwerk/base/src/nsIOService.cpp b/netwerk/base/src/nsIOService.cpp index de9a726b3915..e251d8f6e2e2 100644 --- a/netwerk/base/src/nsIOService.cpp +++ b/netwerk/base/src/nsIOService.cpp @@ -28,6 +28,7 @@ #include "nsIConsoleService.h" #include "nsIUploadChannel2.h" #include "nsXULAppAPI.h" +#include "nsIScriptSecurityManager.h" #include "nsIProtocolProxyCallback.h" #include "nsICancelable.h" #include "nsINetworkLinkService.h" @@ -37,12 +38,19 @@ #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 @@ -131,11 +139,15 @@ int16_t gBadPortList[] = { 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() @@ -148,6 +160,7 @@ nsIOService::nsIOService() , mNetworkLinkServiceInitialized(false) , mChannelEventSinks(NS_CHANNEL_EVENT_SINK_CATEGORY) , mAutoDialEnabled(false) + , mPreviousWifiState(-1) { } @@ -199,6 +212,7 @@ nsIOService::Init() 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"); @@ -901,6 +915,62 @@ nsIOService::GetPrefBranch(nsIPrefBranch **result) 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 networkManager = + do_GetService("@mozilla.org/network/manager;1"); + if (!networkManager) { + return false; + } + nsCOMPtr 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(aUserArg); + if (aValue == nsIAppOfflineInfo::WIFI_ONLY) { + params->service->NotifyAppOfflineStatus(aKey, params->status); + } + return PL_DHASH_NEXT; +} + // nsIObserver interface NS_IMETHODIMP nsIOService::Observe(nsISupports *subject, @@ -956,7 +1026,37 @@ nsIOService::Observe(nsISupports *subject, TrackNetworkLinkStatusForOffline(); } } - + else if (!strcmp(topic, kNetworkActiveChanged)) { +#ifdef MOZ_WIDGET_GONK + if (IsNeckoChild()) { + return NS_OK; + } + nsCOMPtr 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, ¶ms); + } + + mPreviousWifiState = newWifiState; +#endif + } + return NS_OK; } @@ -1250,3 +1350,156 @@ nsIOService::SpeculativeConnect(nsIURI *aURI, 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 observerService = + mozilla::services::GetObserverService(); + MOZ_ASSERT(observerService, "The observer service should not be null"); + + if (observerService) { + nsRefPtr 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; +} diff --git a/netwerk/base/src/nsIOService.h b/netwerk/base/src/nsIOService.h index 1d91615bcf4b..107fb4b5213a 100644 --- a/netwerk/base/src/nsIOService.h +++ b/netwerk/base/src/nsIOService.h @@ -17,6 +17,7 @@ #include "nsIChannelEventSink.h" #include "nsCategoryCache.h" #include "nsISpeculativeConnect.h" +#include "nsDataHashtable.h" #include "mozilla/Attributes.h" #define NS_N(x) (sizeof(x)/sizeof(*x)) @@ -37,6 +38,12 @@ 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 @@ -74,6 +81,9 @@ public: 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 @@ -102,6 +112,11 @@ private: 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; @@ -129,12 +144,50 @@ private: nsTArray mRestrictedPortList; bool mAutoDialEnabled; + + int32_t mPreviousWifiState; + // Hashtable of (appId, nsIAppOffineInfo::mode) pairs + // that is used especially in IsAppOffline + nsDataHashtable 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. */ diff --git a/netwerk/ipc/NeckoChild.cpp b/netwerk/ipc/NeckoChild.cpp index c7a0e77d5a0c..d7d96c91977c 100644 --- a/netwerk/ipc/NeckoChild.cpp +++ b/netwerk/ipc/NeckoChild.cpp @@ -26,6 +26,7 @@ #include "mozilla/net/RtspChannelChild.h" #endif #include "SerializedLoadContext.h" +#include "nsIOService.h" using mozilla::dom::TCPSocketChild; using mozilla::dom::TCPServerSocketChild; @@ -319,5 +320,17 @@ NeckoChild::RecvAsyncAuthPromptForNestedFrame(const uint64_t& aNestedFrameId, return true; } +bool +NeckoChild::RecvAppOfflineStatus(const uint32_t& aId, const bool& aOffline) +{ + // Instantiate the service to make sure gIOService is initialized + nsCOMPtr ioService = do_GetIOService(); + if (gIOService) { + gIOService->SetAppOfflineInternal(aId, aOffline ? + nsIAppOfflineInfo::OFFLINE : nsIAppOfflineInfo::ONLINE); + } + return true; +} + }} // mozilla::net diff --git a/netwerk/ipc/NeckoChild.h b/netwerk/ipc/NeckoChild.h index ba5b7cf2d321..2f6615b4cc80 100644 --- a/netwerk/ipc/NeckoChild.h +++ b/netwerk/ipc/NeckoChild.h @@ -77,6 +77,7 @@ protected: const nsCString& aUri, const nsString& aRealm, const uint64_t& aCallbackId) MOZ_OVERRIDE; + virtual bool RecvAppOfflineStatus(const uint32_t& aId, const bool& aOffline) MOZ_OVERRIDE; }; /** diff --git a/netwerk/ipc/NeckoParent.cpp b/netwerk/ipc/NeckoParent.cpp index 5fc054b550ba..af15a7e7c210 100644 --- a/netwerk/ipc/NeckoParent.cpp +++ b/netwerk/ipc/NeckoParent.cpp @@ -37,6 +37,8 @@ #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; @@ -74,10 +76,15 @@ NeckoParent::NeckoParent() LossyCopyUTF16toASCII(corePath, mCoreAppsBasePath); LossyCopyUTF16toASCII(webPath, mWebAppsBasePath); } + + mObserver = new OfflineObserver(this); } NeckoParent::~NeckoParent() { + if (mObserver) { + mObserver->RemoveObserver(); + } } static PBOverrideStatus @@ -805,4 +812,43 @@ NeckoParent::RecvOnAuthCancelled(const uint64_t& aCallbackId, return true; } +nsresult +NeckoParent::OfflineNotification(nsISupports *aSubject) +{ + nsCOMPtr 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 = + static_cast(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 diff --git a/netwerk/ipc/NeckoParent.h b/netwerk/ipc/NeckoParent.h index 93b6f3317905..1eb150e96ce1 100644 --- a/netwerk/ipc/NeckoParent.h +++ b/netwerk/ipc/NeckoParent.h @@ -7,6 +7,7 @@ #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 @@ -22,8 +23,9 @@ enum PBOverrideStatus { }; // Header file contents -class NeckoParent : - public PNeckoParent +class NeckoParent + : public PNeckoParent + , public DisconnectableParent { public: NeckoParent(); @@ -51,7 +53,8 @@ public: nsCOMPtr &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; @@ -206,6 +209,7 @@ protected: private: nsCString mCoreAppsBasePath; nsCString mWebAppsBasePath; + nsRefPtr mObserver; }; } // namespace net diff --git a/netwerk/ipc/PNecko.ipdl b/netwerk/ipc/PNecko.ipdl index a05358340262..0bc6036ed2ae 100644 --- a/netwerk/ipc/PNecko.ipdl +++ b/netwerk/ipc/PNecko.ipdl @@ -100,6 +100,8 @@ child: */ 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 diff --git a/netwerk/protocol/ftp/FTPChannelParent.cpp b/netwerk/protocol/ftp/FTPChannelParent.cpp index c4a8ce67642d..5cc5798d7ea4 100644 --- a/netwerk/protocol/ftp/FTPChannelParent.cpp +++ b/netwerk/protocol/ftp/FTPChannelParent.cpp @@ -16,6 +16,7 @@ #include "mozilla/ipc/URIUtils.h" #include "mozilla/unused.h" #include "SerializedLoadContext.h" +#include "nsIOService.h" using namespace mozilla::ipc; @@ -37,11 +38,16 @@ FTPChannelParent::FTPChannelParent(nsILoadContext* aLoadContext, PBOverrideStatu 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 @@ -108,7 +114,18 @@ FTPChannelParent::DoAsyncOpen(const URIParams& aURI, 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 ios(do_GetIOService(&rv)); if (NS_FAILED(rv)) return SendFailedAsyncOpen(rv); @@ -173,6 +190,7 @@ FTPChannelParent::RecvCancel(const nsresult& status) { if (mChannel) mChannel->Cancel(status); + return true; } @@ -619,6 +637,25 @@ FTPChannelParent::NotifyDiversionFailed(nsresult aErrorCode, } } +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 //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/ftp/FTPChannelParent.h b/netwerk/protocol/ftp/FTPChannelParent.h index f52c3c605bc5..8365723790de 100644 --- a/netwerk/protocol/ftp/FTPChannelParent.h +++ b/netwerk/protocol/ftp/FTPChannelParent.h @@ -13,6 +13,7 @@ #include "mozilla/net/NeckoParent.h" #include "nsIParentChannel.h" #include "nsIInterfaceRequestor.h" +#include "OfflineObserver.h" class nsFtpChannel; class nsILoadContext; @@ -25,6 +26,7 @@ class FTPChannelParent : public PFTPChannelParent , public nsIInterfaceRequestor , public ADivertableParentChannel , public nsIChannelEventSink + , public DisconnectableParent { public: NS_DECL_ISUPPORTS @@ -79,6 +81,9 @@ protected: 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 mChannel; @@ -103,6 +108,7 @@ protected: // Set if we successfully suspended the nsHttpChannel for diversion. Unset // when we call ResumeForDiversion. bool mSuspendedForDiversion; + nsRefPtr mObserver; }; } // namespace net diff --git a/netwerk/protocol/ftp/nsFtpConnectionThread.cpp b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp index 974dc7d2f620..8983e4807369 100644 --- a/netwerk/protocol/ftp/nsFtpConnectionThread.cpp +++ b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp @@ -2513,7 +2513,11 @@ nsFtpState::CheckCache() // 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 diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp index c38e63ae4653..d8b7de47d94b 100644 --- a/netwerk/protocol/http/HttpChannelParent.cpp +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -27,6 +27,8 @@ #include "SerializedLoadContext.h" #include "nsIAuthInformation.h" #include "nsIAuthPromptCallback.h" +#include "nsIOService.h" +#include "nsICachingChannel.h" using namespace mozilla::dom; using namespace mozilla::ipc; @@ -64,10 +66,15 @@ HttpChannelParent::HttpChannelParent(const PBrowserOrId& iframeEmbedding, } else { mNestedFrameId = iframeEmbedding.get_uint64_t(); } + + mObserver = new OfflineObserver(this); } HttpChannelParent::~HttpChannelParent() { + if (mObserver) { + mObserver->RemoveObserver(); + } } void @@ -160,7 +167,7 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI, 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, @@ -200,6 +207,20 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI, 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 channel; rv = NS_NewChannel(getter_AddRefs(channel), uri, ios, nullptr, nullptr, loadFlags); if (NS_FAILED(rv)) @@ -285,10 +306,8 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI, if (setChooseApplicationCache) { bool inBrowser = false; - uint32_t appId = NECKO_NO_APP_ID; if (mLoadContext) { mLoadContext->GetIsInBrowserElement(&inBrowser); - mLoadContext->GetAppId(&appId); } bool chooseAppCache = false; @@ -333,6 +352,22 @@ HttpChannelParent::ConnectChannel(const uint32_t& channelId) } } + 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; } @@ -1001,6 +1036,25 @@ HttpChannelParent::NotifyDiversionFailed(nsresult aErrorCode, } } +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) diff --git a/netwerk/protocol/http/HttpChannelParent.h b/netwerk/protocol/http/HttpChannelParent.h index 448f1d1f6862..12a0b217076e 100644 --- a/netwerk/protocol/http/HttpChannelParent.h +++ b/netwerk/protocol/http/HttpChannelParent.h @@ -13,6 +13,8 @@ #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" @@ -38,6 +40,7 @@ class HttpChannelParent : public PHttpChannelParent , public nsIInterfaceRequestor , public ADivertableParentChannel , public nsIAuthPromptProvider + , public DisconnectableParent { virtual ~HttpChannelParent(); @@ -126,6 +129,9 @@ protected: friend class HttpChannelParentListener; nsRefPtr mTabParent; + void OfflineDisconnect() MOZ_OVERRIDE; + uint32_t GetAppId() MOZ_OVERRIDE; + private: nsRefPtr mChannel; nsCOMPtr mCacheEntry; @@ -147,6 +153,8 @@ private: bool mSentRedirect1BeginFailed : 1; bool mReceivedRedirect2Verify : 1; + nsRefPtr mObserver; + PBOverrideStatus mPBOverride; nsCOMPtr mLoadContext; diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 7ff6cfd82777..29e5487d7db3 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -2561,8 +2561,16 @@ nsHttpChannel::OpenCacheEntry(bool usingSSL) 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; diff --git a/netwerk/protocol/websocket/WebSocketChannelParent.cpp b/netwerk/protocol/websocket/WebSocketChannelParent.cpp index 99dcbb1bf0f8..9d50bae5e894 100644 --- a/netwerk/protocol/websocket/WebSocketChannelParent.cpp +++ b/netwerk/protocol/websocket/WebSocketChannelParent.cpp @@ -10,6 +10,8 @@ #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; @@ -33,8 +35,15 @@ WebSocketChannelParent::WebSocketChannelParent(nsIAuthPromptProvider* aAuthProvi if (!webSocketLog) webSocketLog = PR_NewLogModule("nsWebSocket"); #endif + mObserver = new OfflineObserver(this); } +WebSocketChannelParent::~WebSocketChannelParent() +{ + if (mObserver) { + mObserver->RemoveObserver(); + } +} //----------------------------------------------------------------------------- // WebSocketChannelParent::PWebSocketChannelParent //----------------------------------------------------------------------------- @@ -63,6 +72,17 @@ WebSocketChannelParent::RecvAsyncOpen(const URIParams& aURI, nsresult rv; nsCOMPtr 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); @@ -117,6 +137,7 @@ WebSocketChannelParent::RecvClose(const uint16_t& code, const nsCString& reason) nsresult rv = mChannel->Close(code, reason); NS_ENSURE_SUCCESS(rv, true); } + return true; } @@ -258,6 +279,24 @@ WebSocketChannelParent::GetInterface(const nsIID & iid, void **result) 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 diff --git a/netwerk/protocol/websocket/WebSocketChannelParent.h b/netwerk/protocol/websocket/WebSocketChannelParent.h index e315d0d816de..b4e9e2fbd63f 100644 --- a/netwerk/protocol/websocket/WebSocketChannelParent.h +++ b/netwerk/protocol/websocket/WebSocketChannelParent.h @@ -15,6 +15,7 @@ #include "nsILoadContext.h" #include "nsCOMPtr.h" #include "nsString.h" +#include "OfflineObserver.h" class nsIAuthPromptProvider; @@ -23,10 +24,10 @@ namespace net { class WebSocketChannelParent : public PWebSocketParent, public nsIWebSocketListener, + public DisconnectableParent, public nsIInterfaceRequestor { - ~WebSocketChannelParent() {} - + ~WebSocketChannelParent(); public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIWEBSOCKETLISTENER @@ -54,6 +55,10 @@ class WebSocketChannelParent : public PWebSocketParent, void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE; + void OfflineDisconnect() MOZ_OVERRIDE; + uint32_t GetAppId() MOZ_OVERRIDE; + nsRefPtr mObserver; + nsCOMPtr mAuthProvider; nsCOMPtr mChannel; nsCOMPtr mLoadContext; diff --git a/netwerk/test/unit_ipc/child_app_offline.js b/netwerk/test/unit_ipc/child_app_offline.js new file mode 100644 index 000000000000..6193774000d4 --- /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(); +} diff --git a/netwerk/test/unit_ipc/test_app_offline_http.js b/netwerk/test/unit_ipc/test_app_offline_http.js new file mode 100644 index 000000000000..81544728565a --- /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(); +} diff --git a/netwerk/test/unit_ipc/xpcshell.ini b/netwerk/test/unit_ipc/xpcshell.ini index cebb01062ace..7dc605148c28 100644 --- a/netwerk/test/unit_ipc/xpcshell.ini +++ b/netwerk/test/unit_ipc/xpcshell.ini @@ -2,6 +2,7 @@ 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] @@ -34,3 +35,4 @@ skip-if = true [test_XHR_redirects.js] [test_redirect_history_wrap.js] [test_reply_without_content_type_wrap.js] +[test_app_offline_http.js]