From 087d426a86a5750a907e15fecc54785c1bb78f9b Mon Sep 17 00:00:00 2001 From: Valentin Gosu Date: Sat, 23 Aug 2014 06:05:56 +0300 Subject: [PATCH] Bug 786419 - Part 1 - Provide way to "set network offline" per app r=jduell --- netwerk/base/public/nsIIOService.idl | 36 +++ netwerk/base/src/OfflineObserver.cpp | 124 ++++++++++ netwerk/base/src/OfflineObserver.h | 73 ++++++ netwerk/base/src/moz.build | 2 + netwerk/base/src/nsIOService.cpp | 234 ++++++++++++++++++ netwerk/base/src/nsIOService.h | 52 ++++ netwerk/ipc/NeckoChild.cpp | 13 + netwerk/ipc/NeckoChild.h | 1 + netwerk/ipc/NeckoParent.cpp | 46 ++++ netwerk/ipc/NeckoParent.h | 9 +- netwerk/ipc/PNecko.ipdl | 2 + netwerk/protocol/ftp/FTPChannelParent.cpp | 37 +++ netwerk/protocol/ftp/FTPChannelParent.h | 6 + netwerk/protocol/http/HttpChannelParent.cpp | 55 +++- netwerk/protocol/http/HttpChannelParent.h | 8 + netwerk/protocol/http/nsHttpChannel.cpp | 10 +- .../websocket/WebSocketChannelParent.cpp | 39 +++ .../websocket/WebSocketChannelParent.h | 9 +- 18 files changed, 747 insertions(+), 9 deletions(-) create mode 100644 netwerk/base/src/OfflineObserver.cpp create mode 100644 netwerk/base/src/OfflineObserver.h 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/src/OfflineObserver.cpp b/netwerk/base/src/OfflineObserver.cpp new file mode 100644 index 000000000000..0c031e3d7dc5 --- /dev/null +++ b/netwerk/base/src/OfflineObserver.cpp @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "OfflineObserver.h" +#include "nsNetUtil.h" +#include "nsIOService.h" +#include "mozilla/net/NeckoCommon.h" +#include "nsIObserverService.h" +#include "nsThreadUtils.h" +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(OfflineObserver, nsIObserver) + +void +OfflineObserver::RegisterOfflineObserver() +{ + if (NS_IsMainThread()) { + RegisterOfflineObserverMainThread(); + } else { + nsRefPtr 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; +} + +uint32_t +DisconnectableParent::GetAppId() +{ + return NECKO_UNKNOWN_APP_ID; +} + +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..e155a5b02dc9 --- /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(); + + // 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 0fb2fc3a97c1..21ffeb003773 100644 --- a/netwerk/base/src/moz.build +++ b/netwerk/base/src/moz.build @@ -17,6 +17,7 @@ EXPORTS.mozilla.net += [ 'ChannelDiverterParent.h', 'Dashboard.h', 'DashboardTypes.h', + 'OfflineObserver.h', ] UNIFIED_SOURCES += [ @@ -85,6 +86,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 231707ceab34..6b02ebc0997a 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" @@ -39,6 +40,10 @@ #include "MainThreadUtils.h" #include "nsIWidget.h" +#ifdef MOZ_WIDGET_GONK +#include "nsINetworkManager.h" +#endif + #if defined(XP_WIN) #include "nsNativeConnectionHelper.h" #endif @@ -133,11 +138,14 @@ 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() @@ -151,6 +159,7 @@ nsIOService::nsIOService() , mChannelEventSinks(NS_CHANNEL_EVENT_SINK_CATEGORY) , mAutoDialEnabled(false) , mNetworkNotifyChanged(true) + , mPreviousWifiState(-1) { } @@ -204,6 +213,7 @@ nsIOService::Init() observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, true); observerService->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true); + observerService->AddObserver(this, kNetworkActiveChanged, true); } else NS_WARNING("failed to get observer service"); @@ -914,6 +924,61 @@ nsIOService::GetPrefBranch(nsIPrefBranch **result) CallGetService(NS_PREFSERVICE_CONTRACTID, result); } +// This returns true if wifi-only apps should have connectivity. +static bool +IsWifiActive() +{ + // We don't need to do this check inside the child process + if (XRE_GetProcessType() != GeckoProcessType_Default) { + return false; + } +#ifdef MOZ_WIDGET_GONK + // On B2G we query the network manager for the active interface + nsCOMPtr 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, @@ -976,6 +1041,35 @@ nsIOService::Observe(nsISupports *subject, NS_NETWORK_LINK_TOPIC, MOZ_UTF16(NS_NETWORK_LINK_DATA_CHANGED)); } + } else if (!strcmp(topic, kNetworkActiveChanged)) { +#ifdef MOZ_WIDGET_GONK + if (IsNeckoChild()) { + return NS_OK; + } + nsCOMPtr 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; @@ -1288,3 +1382,143 @@ 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(XRE_GetProcessType() == GeckoProcessType_Default, + NS_ERROR_FAILURE); + NS_ENSURE_TRUE(aAppId != nsIScriptSecurityManager::NO_APP_ID, + NS_ERROR_INVALID_ARG); + NS_ENSURE_TRUE(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID, + NS_ERROR_INVALID_ARG); + + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(new SetAppOfflineMainThread(aAppId, aState)); + return NS_OK; + } + + SetAppOfflineInternal(aAppId, aState); + + return NS_OK; +} + +void +nsIOService::SetAppOfflineInternal(uint32_t aAppId, int32_t aState) +{ + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_TRUE_VOID(NS_IsMainThread()); + + int32_t state; + if (mAppsOfflineStatus.Get(aAppId, &state) && state == aState) { + // The app is already in this state. Nothing needs to be done. + return; + } + + bool wifiActive = IsWifiActive(); + bool offline = (state == nsIAppOfflineInfo::OFFLINE) || + (state == nsIAppOfflineInfo::WIFI_ONLY && !wifiActive); + + switch (aState) { + case nsIAppOfflineInfo::OFFLINE: + mAppsOfflineStatus.Put(aAppId, nsIAppOfflineInfo::OFFLINE); + if (!offline) { + NotifyAppOfflineStatus(aAppId, nsIAppOfflineInfo::OFFLINE); + } + break; + case nsIAppOfflineInfo::WIFI_ONLY: + mAppsOfflineStatus.Put(aAppId, nsIAppOfflineInfo::WIFI_ONLY); + if (offline && wifiActive) { + NotifyAppOfflineStatus(aAppId, nsIAppOfflineInfo::ONLINE); + } else if (!offline && !wifiActive) { + NotifyAppOfflineStatus(aAppId, nsIAppOfflineInfo::OFFLINE); + } + break; + case nsIAppOfflineInfo::ONLINE: + mAppsOfflineStatus.Remove(aAppId); + if (offline) { + NotifyAppOfflineStatus(aAppId, nsIAppOfflineInfo::ONLINE); + } + break; + default: + break; + } + +} + +NS_IMETHODIMP +nsIOService::IsAppOffline(uint32_t aAppId, bool* aResult) +{ + NS_ENSURE_ARG(aResult); + *aResult = mOffline; + + if (mOffline) { + // If the entire browser is offline, return that status + return NS_OK; + } + + if (aAppId == NECKO_NO_APP_ID || + aAppId == NECKO_UNKNOWN_APP_ID) { + return NS_ERROR_NOT_AVAILABLE; + } + + int32_t state; + if (mAppsOfflineStatus.Get(aAppId, &state)) { + switch (state) { + case nsIAppOfflineInfo::OFFLINE: + *aResult = true; + break; + case nsIAppOfflineInfo::WIFI_ONLY: + *aResult = !IsWifiActive(); + break; + default: + // The app is online by default + break; + } + } + + return NS_OK; +} diff --git a/netwerk/base/src/nsIOService.h b/netwerk/base/src/nsIOService.h index 2839d9b057d5..ef1190fb549e 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; @@ -130,12 +145,49 @@ private: bool mAutoDialEnabled; bool mNetworkNotifyChanged; + 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 6fd1d1694b2d..9596c3f44436 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; @@ -317,5 +318,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 5d3f6d0168c0..874cf6386ea0 100644 --- a/netwerk/ipc/NeckoChild.h +++ b/netwerk/ipc/NeckoChild.h @@ -75,6 +75,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 002d0625ae9f..51cba3f9d055 100644 --- a/netwerk/ipc/NeckoParent.cpp +++ b/netwerk/ipc/NeckoParent.cpp @@ -37,6 +37,8 @@ #include "nsAuthInformationHolder.h" #include "nsIAuthPromptCallback.h" #include "nsPrincipal.h" +#include "nsIOService.h" +#include "mozilla/net/OfflineObserver.h" using mozilla::dom::ContentParent; using mozilla::dom::TabParent; @@ -74,10 +76,15 @@ NeckoParent::NeckoParent() LossyCopyUTF16toASCII(corePath, mCoreAppsBasePath); LossyCopyUTF16toASCII(webPath, mWebAppsBasePath); } + + mObserver = new OfflineObserver(this); } NeckoParent::~NeckoParent() { + if (mObserver) { + mObserver->RemoveObserver(); + } } static PBOverrideStatus @@ -796,4 +803,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 6d033c12c991..0a0c399b3832 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,7 @@ public: nsCOMPtr &aResult); virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE; - + virtual nsresult OfflineNotification(nsISupports *) MOZ_OVERRIDE; virtual void CloneManagees(ProtocolBase* aSource, mozilla::ipc::ProtocolCloneContext* aCtx) MOZ_OVERRIDE; @@ -201,6 +203,7 @@ protected: private: nsCString mCoreAppsBasePath; nsCString mWebAppsBasePath; + nsRefPtr mObserver; }; } // namespace net diff --git a/netwerk/ipc/PNecko.ipdl b/netwerk/ipc/PNecko.ipdl index cbbc150de1d7..b19d0e27239a 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 3d94e6c16885..4ba9129cbb87 100644 --- a/netwerk/protocol/ftp/FTPChannelParent.cpp +++ b/netwerk/protocol/ftp/FTPChannelParent.cpp @@ -18,6 +18,7 @@ #include "SerializedLoadContext.h" #include "nsIContentPolicy.h" #include "mozilla/ipc/BackgroundUtils.h" +#include "nsIOService.h" using namespace mozilla::ipc; @@ -39,11 +40,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 @@ -115,6 +121,17 @@ 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)); + } + + if (app_offline) + return SendFailedAsyncOpen(NS_ERROR_OFFLINE); + nsresult rv; nsCOMPtr ios(do_GetIOService(&rv)); if (NS_FAILED(rv)) @@ -196,6 +213,7 @@ FTPChannelParent::RecvCancel(const nsresult& status) { if (mChannel) mChannel->Cancel(status); + return true; } @@ -642,6 +660,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 46a717323f5a..fc6471fb8b23 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 @@ -82,6 +84,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; @@ -106,6 +111,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/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp index fb83eae9dcc3..34d01633ade4 100644 --- a/netwerk/protocol/http/HttpChannelParent.cpp +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -29,6 +29,9 @@ #include "nsIAuthPromptCallback.h" #include "nsIContentPolicy.h" #include "mozilla/ipc/BackgroundUtils.h" +#include "nsIOService.h" +#include "nsICachingChannel.h" + using namespace mozilla::dom; using namespace mozilla::ipc; @@ -66,10 +69,15 @@ HttpChannelParent::HttpChannelParent(const PBrowserOrId& iframeEmbedding, } else { mNestedFrameId = iframeEmbedding.get_uint64_t(); } + + mObserver = new OfflineObserver(this); } HttpChannelParent::~HttpChannelParent() { + if (mObserver) { + mObserver->RemoveObserver(); + } } void @@ -164,7 +172,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, @@ -213,6 +221,18 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI, return SendFailedAsyncOpen(rv); } + bool appOffline = false; + uint32_t appId = GetAppId(); + if (appId != NECKO_UNKNOWN_APP_ID && + appId != NECKO_NO_APP_ID) { + gIOService->IsAppOffline(appId, &appOffline); + } + + uint32_t loadFlags = aLoadFlags; + if (appOffline) { + loadFlags |= nsICachingChannel::LOAD_ONLY_FROM_CACHE; + } + nsCOMPtr channel; rv = NS_NewChannel(getter_AddRefs(channel), uri, @@ -309,10 +329,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; @@ -357,6 +375,18 @@ 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) { + mChannel->Cancel(NS_ERROR_OFFLINE); + mStatus = NS_ERROR_OFFLINE; + } + return true; } @@ -1036,6 +1066,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 7e376c4d7f6c..f7cfb9cd3508 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(); @@ -129,6 +132,9 @@ protected: friend class HttpChannelParentListener; nsRefPtr mTabParent; + void OfflineDisconnect() MOZ_OVERRIDE; + uint32_t GetAppId() MOZ_OVERRIDE; + private: nsRefPtr mChannel; nsCOMPtr mCacheEntry; @@ -150,6 +156,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 0e1c40de2d19..87ea5991272f 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -2584,8 +2584,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;