diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index c8f72162345..84d57bb794a 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -940,10 +940,6 @@ pref("consoleservice.buffered", false); pref("toolkit.storage.pageSize", 2048); #endif -// Enable captive portal detection. -pref("captivedetect.canonicalURL", "http://detectportal.firefox.com/success.txt"); -pref("captivedetect.canonicalContent", "success\n"); - // The url of the manifest we use for ADU pings. pref("ping.manifestURL", "https://marketplace.firefox.com/packaged.webapp"); diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 7e285e413f2..45f7352416e 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4620,6 +4620,14 @@ pref("ui.touch_activation.duration_ms", 10); // actions when the fifo is written to. Disable this in general. pref("memory_info_dumper.watch_fifo.enabled", false); +// If minInterval is 0, the check will only happen +// when the service has a strong suspicion we are in a captive portal +pref("network.captive-portal-service.minInterval", 60000); // 60 seconds +pref("network.captive-portal-service.maxInterval", 1500000); // 25 minutes +// Every 10 checks, the delay is increased by a factor of 5 +pref("network.captive-portal-service.backoffFactor", "5.0"); +pref("network.captive-portal-service.enabled", false); + pref("captivedetect.canonicalURL", "http://detectportal.firefox.com/success.txt"); pref("captivedetect.canonicalContent", "success\n"); pref("captivedetect.maxWaitingTime", 5000); diff --git a/netwerk/base/CaptivePortalService.cpp b/netwerk/base/CaptivePortalService.cpp new file mode 100644 index 00000000000..37f6b082b7a --- /dev/null +++ b/netwerk/base/CaptivePortalService.cpp @@ -0,0 +1,311 @@ +/* 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 "CaptivePortalService.h" +#include "mozilla/Services.h" +#include "mozilla/Preferences.h" +#include "nsIObserverService.h" +#include "nsXULAppAPI.h" + +#define kInterfaceName "captive-portal-inteface" + +static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login"; +static const char kAbortCaptivePortalLoginEvent[] = "captive-portal-login-abort"; +static const char kCaptivePortalLoginSuccessEvent[] = "captive-portal-login-success"; + +static const uint32_t kDefaultInterval = 60*1000; // check every 60 seconds + +static PRLogModuleInfo *gCaptivePortalLog = nullptr; +#undef LOG +#define LOG(args) MOZ_LOG(gCaptivePortalLog, mozilla::LogLevel::Debug, args) + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(CaptivePortalService, nsICaptivePortalService, nsIObserver, + nsISupportsWeakReference, nsITimerCallback, + nsICaptivePortalCallback) + +CaptivePortalService::CaptivePortalService() + : mState(UNKNOWN) + , mStarted(false) + , mInitialized(false) + , mRequestInProgress(false) + , mEverBeenCaptive(false) + , mDelay(kDefaultInterval) + , mSlackCount(0) + , mMinInterval(kDefaultInterval) + , mMaxInterval(25*kDefaultInterval) + , mBackoffFactor(5.0) +{ + mLastChecked = TimeStamp::Now(); +} + +CaptivePortalService::~CaptivePortalService() +{ +} + +nsresult +CaptivePortalService::PerformCheck() +{ + LOG(("CaptivePortalService::PerformCheck mRequestInProgress:%d mInitialized:%d mStarted:%d\n", + mRequestInProgress, mInitialized, mStarted)); + // Don't issue another request if last one didn't complete + if (mRequestInProgress || !mInitialized || !mStarted) { + return NS_OK; + } + + nsresult rv; + if (!mCaptivePortalDetector) { + mCaptivePortalDetector = + do_GetService("@mozilla.org/toolkit/captive-detector;1", &rv); + if (NS_FAILED(rv)) { + LOG(("Unable to get a captive portal detector\n")); + return rv; + } + } + + LOG(("CaptivePortalService::PerformCheck - Calling CheckCaptivePortal\n")); + mRequestInProgress = true; + mCaptivePortalDetector->CheckCaptivePortal( + NS_LITERAL_STRING(kInterfaceName).get(), this); + return NS_OK; +} + +nsresult +CaptivePortalService::RearmTimer() +{ + // Start a timer to recheck + if (mTimer) { + mTimer->Cancel(); + } + + if (!mTimer) { + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + } + + if (mTimer && mDelay > 0) { + LOG(("CaptivePortalService - Reloading timer with delay %u\n", mDelay)); + return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT); + } + + return NS_OK; +} + +nsresult +CaptivePortalService::Initialize() +{ + if (mInitialized || XRE_GetProcessType() != GeckoProcessType_Default) { + return NS_OK; + } + mInitialized = true; + + if (!gCaptivePortalLog) { + gCaptivePortalLog = PR_NewLogModule("CaptivePortalService"); + } + + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->AddObserver(this, kOpenCaptivePortalLoginEvent, true); + observerService->AddObserver(this, kAbortCaptivePortalLoginEvent, true); + observerService->AddObserver(this, kCaptivePortalLoginSuccessEvent, true); + } + + LOG(("Initialized CaptivePortalService\n")); + return NS_OK; +} + +nsresult +CaptivePortalService::Start() +{ + if (!mInitialized) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (mStarted) { + return NS_OK; + } + + mStarted = true; + mEverBeenCaptive = false; + + // Get the delay prefs + Preferences::GetUint("network.captive-portal-service.minInterval", &mMinInterval); + Preferences::GetUint("network.captive-portal-service.maxInterval", &mMaxInterval); + Preferences::GetFloat("network.captive-portal-service.backoffFactor", &mBackoffFactor); + + LOG(("CaptivePortalService::Start min:%u max:%u backoff:%.2f\n", + mMinInterval, mMaxInterval, mBackoffFactor)); + + mSlackCount = 0; + mDelay = mMinInterval; + + // When Start is called, perform a check immediately + PerformCheck(); + RearmTimer(); + return NS_OK; +} + +nsresult +CaptivePortalService::Stop() +{ + LOG(("CaptivePortalService::Stop\n")); + + if (!mStarted) { + return NS_OK; + } + + if (mTimer) { + mTimer->Cancel(); + } + mTimer = nullptr; + mRequestInProgress = false; + mStarted = false; + if (mCaptivePortalDetector) { + mCaptivePortalDetector->Abort(NS_LITERAL_STRING(kInterfaceName).get()); + } + mCaptivePortalDetector = nullptr; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsICaptivePortalService +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +CaptivePortalService::GetState(int32_t *aState) +{ + *aState = UNKNOWN; + if (!mInitialized) { + return NS_ERROR_NOT_INITIALIZED; + } + + *aState = mState; + return NS_OK; +} + +NS_IMETHODIMP +CaptivePortalService::RecheckCaptivePortal() +{ + LOG(("CaptivePortalService::RecheckCaptivePortal\n")); + + // This is called for user activity. We need to reset the slack count, + // so the checks continue to be quite frequent. + mSlackCount = 0; + mDelay = mMinInterval; + + PerformCheck(); + RearmTimer(); + return NS_OK; +} + +NS_IMETHODIMP +CaptivePortalService::GetLastChecked(uint64_t *aLastChecked) +{ + double duration = (TimeStamp::Now() - mLastChecked).ToMilliseconds(); + *aLastChecked = static_cast(duration); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsITimer +// This callback gets called every mDelay miliseconds +// It issues a checkCaptivePortal operation if one isn't already in progress +//----------------------------------------------------------------------------- +NS_IMETHODIMP +CaptivePortalService::Notify(nsITimer *aTimer) +{ + LOG(("CaptivePortalService::Notify\n")); + MOZ_ASSERT(aTimer == mTimer); + + PerformCheck(); + + // This is needed because we don't want to always make requests very often. + // Every 10 checks, we the delay is increased mBackoffFactor times + // to a maximum delay of mMaxInterval + mSlackCount++; + if (mSlackCount % 10 == 0) { + mDelay = mDelay * mBackoffFactor; + } + if (mDelay > mMaxInterval) { + mDelay = mMaxInterval; + } + + // Note - if mDelay is 0, the timer will not be rearmed. + RearmTimer(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsIObserver +//----------------------------------------------------------------------------- +NS_IMETHODIMP +CaptivePortalService::Observe(nsISupports *aSubject, + const char * aTopic, + const char16_t * aData) +{ + LOG(("CaptivePortalService::Observe() topic=%s\n", aTopic)); + if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) { + // A redirect or altered content has been detected. + // The user needs to log in. We are in a captive portal. + mState = LOCKED_PORTAL; + mLastChecked = TimeStamp::Now(); + mEverBeenCaptive = true; + } else if (!strcmp(aTopic, kCaptivePortalLoginSuccessEvent)) { + // The user has successfully logged in. We have connectivity. + mState = UNLOCKED_PORTAL; + mLastChecked = TimeStamp::Now(); + mRequestInProgress = false; + mSlackCount = 0; + mDelay = mMinInterval; + RearmTimer(); + } else if (!strcmp(aTopic, kAbortCaptivePortalLoginEvent)) { + // The login has been aborted + mRequestInProgress = false; + mState = UNKNOWN; + mLastChecked = TimeStamp::Now(); + mSlackCount = 0; + } + return NS_OK; +} + +//----------------------------------------------------------------------------- +// CaptivePortalService::nsICaptivePortalCallback +//----------------------------------------------------------------------------- +NS_IMETHODIMP +CaptivePortalService::Prepare() +{ + LOG(("CaptivePortalService::Prepare\n")); + // XXX: Finish preparation shouldn't be called until dns and routing is available. + if (mCaptivePortalDetector) { + mCaptivePortalDetector->FinishPreparation(NS_LITERAL_STRING(kInterfaceName).get()); + } + return NS_OK; +} + +NS_IMETHODIMP +CaptivePortalService::Complete(bool success) +{ + LOG(("CaptivePortalService::Complete(success=%d) mState=%d\n", success, mState)); + mLastChecked = TimeStamp::Now(); + if ((mState == UNKNOWN || mState == NOT_CAPTIVE) && success) { + mState = NOT_CAPTIVE; + // If this check succeeded and we have never been in a captive portal + // since the service was started, there is no need to keep polling + if (!mEverBeenCaptive) { + mDelay = 0; + if (mTimer) { + mTimer->Cancel(); + } + } + } + + mRequestInProgress = false; + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/CaptivePortalService.h b/netwerk/base/CaptivePortalService.h new file mode 100644 index 00000000000..23ab5defa15 --- /dev/null +++ b/netwerk/base/CaptivePortalService.h @@ -0,0 +1,65 @@ +/* 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 CaptivePortalService_h_ +#define CaptivePortalService_h_ + +#include "nsICaptivePortalService.h" +#include "nsICaptivePortalDetector.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" +#include "nsITimer.h" +#include "nsCOMArray.h" + +namespace mozilla { +namespace net { + +class CaptivePortalService + : public nsICaptivePortalService + , public nsIObserver + , public nsSupportsWeakReference + , public nsITimerCallback + , public nsICaptivePortalCallback +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICAPTIVEPORTALSERVICE + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSICAPTIVEPORTALCALLBACK + + CaptivePortalService(); + nsresult Initialize(); + nsresult Start(); + nsresult Stop(); +private: + virtual ~CaptivePortalService(); + nsresult PerformCheck(); + nsresult RearmTimer(); + + nsCOMPtr mCaptivePortalDetector; + int32_t mState; + + nsCOMPtr mTimer; + bool mStarted; + bool mInitialized; + bool mRequestInProgress; + bool mEverBeenCaptive; + + uint32_t mDelay; + int32_t mSlackCount; + + uint32_t mMinInterval; + uint32_t mMaxInterval; + float mBackoffFactor; + + // This holds a timestamp when the last time when the captive portal check + // has changed state. + mozilla::TimeStamp mLastChecked; +}; + +} // namespace net +} // namespace mozilla + +#endif // CaptivePortalService_h_ diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build index 7882e827cf5..45796181a55 100644 --- a/netwerk/base/moz.build +++ b/netwerk/base/moz.build @@ -27,6 +27,7 @@ XPIDL_SOURCES += [ 'nsICacheInfoChannel.idl', 'nsICachingChannel.idl', 'nsICancelable.idl', + 'nsICaptivePortalService.idl', 'nsIChannel.idl', 'nsIChannelEventSink.idl', 'nsIChildChannel.idl', @@ -164,6 +165,7 @@ EXPORTS.mozilla += [ ] EXPORTS.mozilla.net += [ + 'CaptivePortalService.h', 'ChannelDiverterChild.h', 'ChannelDiverterParent.h', 'Dashboard.h', @@ -181,6 +183,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': UNIFIED_SOURCES += [ 'ArrayBufferInputStream.cpp', 'BackgroundFileSaver.cpp', + 'CaptivePortalService.cpp', 'ChannelDiverterChild.cpp', 'ChannelDiverterParent.cpp', 'Dashboard.cpp', diff --git a/netwerk/base/nsICaptivePortalService.idl b/netwerk/base/nsICaptivePortalService.idl new file mode 100644 index 00000000000..50712d72359 --- /dev/null +++ b/netwerk/base/nsICaptivePortalService.idl @@ -0,0 +1,44 @@ +/* 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 "nsISupports.idl" + +[scriptable, uuid(b5fd5629-d04c-4138-9529-9311f291ecd4)] +interface nsICaptivePortalServiceCallback : nsISupports +{ + /** + * Invoke callbacks after captive portal detection finished. + */ + void complete(in bool success, in nsresult error); +}; + +/** + * Service used for captive portal detection. + */ +[scriptable, uuid(bdbe0555-fc3d-4f7b-9205-c309ceb2d641)] +interface nsICaptivePortalService : nsISupports +{ + const long UNKNOWN = 0; + const long NOT_CAPTIVE = 1; + const long UNLOCKED_PORTAL = 2; + const long LOCKED_PORTAL = 3; + + /** + * Called from XPCOM to trigger a captive portal recheck. + * A network request will only be performed if no other checks are currently + * ongoing. + */ + void recheckCaptivePortal(); + + /** + * Returns the state of the captive portal. + */ + readonly attribute long state; + + /** + * Returns the time difference between NOW and the last time a request was + * completed in milliseconds. + */ + readonly attribute unsigned long long lastChecked; +}; diff --git a/netwerk/build/nsNetCID.h b/netwerk/build/nsNetCID.h index 66ebaf42ce4..e816906ab68 100644 --- a/netwerk/build/nsNetCID.h +++ b/netwerk/build/nsNetCID.h @@ -469,6 +469,17 @@ { 0xae, 0xcf, 0x05, 0xf8, 0xfa, 0xf0, 0x0c, 0x9b } \ } +// captive portal service implementing nsICaptivePortalService +#define NS_CAPTIVEPORTAL_CONTRACTID \ + "@mozilla.org/network/captive-portal-service;1" +#define NS_CAPTIVEPORTAL_CID \ +{ /* bdbe0555-fc3d-4f7b-9205-c309ceb2d641 */ \ + 0xbdbe0555, \ + 0xfc3d, \ + 0x4f7b, \ + { 0x92, 0x05, 0xc3, 0x09, 0xce, 0xb2, 0xd6, 0x41 } \ +} + /****************************************************************************** * netwerk/cache/ classes */ diff --git a/netwerk/build/nsNetModule.cpp b/netwerk/build/nsNetModule.cpp index 258bdea8ee7..f0399e82a78 100644 --- a/netwerk/build/nsNetModule.cpp +++ b/netwerk/build/nsNetModule.cpp @@ -132,6 +132,16 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(RedirectChannelRegistrar) typedef mozilla::net::CacheStorageService CacheStorageService; NS_GENERIC_FACTORY_CONSTRUCTOR(CacheStorageService) + +/////////////////////////////////////////////////////////////////////////////// + +#include "mozilla/net/CaptivePortalService.h" +namespace mozilla { +namespace net { + NS_GENERIC_FACTORY_CONSTRUCTOR(CaptivePortalService) +} +} + /////////////////////////////////////////////////////////////////////////////// extern nsresult @@ -809,6 +819,7 @@ NS_DEFINE_NAMED_CID(NS_SERIALIZATION_HELPER_CID); NS_DEFINE_NAMED_CID(NS_REDIRECTCHANNELREGISTRAR_CID); NS_DEFINE_NAMED_CID(NS_CACHE_STORAGE_SERVICE_CID); NS_DEFINE_NAMED_CID(NS_NETWORKPREDICTOR_CID); +NS_DEFINE_NAMED_CID(NS_CAPTIVEPORTAL_CID); static const mozilla::Module::CIDEntry kNeckoCIDs[] = { { &kNS_IOSERVICE_CID, false, nullptr, nsIOServiceConstructor }, @@ -956,6 +967,7 @@ static const mozilla::Module::CIDEntry kNeckoCIDs[] = { { &kNS_REDIRECTCHANNELREGISTRAR_CID, false, nullptr, RedirectChannelRegistrarConstructor }, { &kNS_CACHE_STORAGE_SERVICE_CID, false, nullptr, CacheStorageServiceConstructor }, { &kNS_NETWORKPREDICTOR_CID, false, nullptr, mozilla::net::Predictor::Create }, + { &kNS_CAPTIVEPORTAL_CID, false, nullptr, mozilla::net::CaptivePortalServiceConstructor }, { nullptr } }; @@ -1107,6 +1119,7 @@ static const mozilla::Module::ContractIDEntry kNeckoContracts[] = { { NS_CACHE_STORAGE_SERVICE_CONTRACTID, &kNS_CACHE_STORAGE_SERVICE_CID }, { NS_CACHE_STORAGE_SERVICE_CONTRACTID2, &kNS_CACHE_STORAGE_SERVICE_CID }, { NS_NETWORKPREDICTOR_CONTRACTID, &kNS_NETWORKPREDICTOR_CID }, + { NS_CAPTIVEPORTAL_CONTRACTID, &kNS_CAPTIVEPORTAL_CID }, { nullptr } };