1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "mozilla/net/CaptivePortalService.h"
6 #include "mozilla/ClearOnShutdown.h"
7 #include "mozilla/Services.h"
8 #include "mozilla/Preferences.h"
9 #include "nsIObserverService.h"
10 #include "nsServiceManagerUtils.h"
11 #include "nsXULAppAPI.h"
12 #include "xpcpublic.h"
14 static NS_NAMED_LITERAL_STRING(kInterfaceName
, u
"captive-portal-inteface");
16 static const char kOpenCaptivePortalLoginEvent
[] = "captive-portal-login";
17 static const char kAbortCaptivePortalLoginEvent
[] =
18 "captive-portal-login-abort";
19 static const char kCaptivePortalLoginSuccessEvent
[] =
20 "captive-portal-login-success";
22 static const uint32_t kDefaultInterval
= 60 * 1000; // check every 60 seconds
27 static LazyLogModule
gCaptivePortalLog("CaptivePortalService");
29 #define LOG(args) MOZ_LOG(gCaptivePortalLog, mozilla::LogLevel::Debug, args)
31 NS_IMPL_ISUPPORTS(CaptivePortalService
, nsICaptivePortalService
, nsIObserver
,
32 nsISupportsWeakReference
, nsITimerCallback
,
33 nsICaptivePortalCallback
, nsINamed
)
35 static StaticRefPtr
<CaptivePortalService
> gCPService
;
38 already_AddRefed
<nsICaptivePortalService
> CaptivePortalService::GetSingleton() {
40 return do_AddRef(gCPService
);
43 gCPService
= new CaptivePortalService();
44 ClearOnShutdown(&gCPService
);
45 return do_AddRef(gCPService
);
48 CaptivePortalService::CaptivePortalService()
52 mRequestInProgress(false),
53 mEverBeenCaptive(false),
54 mDelay(kDefaultInterval
),
56 mMinInterval(kDefaultInterval
),
57 mMaxInterval(25 * kDefaultInterval
),
59 mLastChecked
= TimeStamp::Now();
62 CaptivePortalService::~CaptivePortalService() {
63 LOG(("CaptivePortalService::~CaptivePortalService isParentProcess:%d\n",
64 XRE_GetProcessType() == GeckoProcessType_Default
));
67 nsresult
CaptivePortalService::PerformCheck() {
69 ("CaptivePortalService::PerformCheck mRequestInProgress:%d "
70 "mInitialized:%d mStarted:%d\n",
71 mRequestInProgress
, mInitialized
, mStarted
));
72 // Don't issue another request if last one didn't complete
73 if (mRequestInProgress
|| !mInitialized
|| !mStarted
) {
76 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default
);
78 if (!mCaptivePortalDetector
) {
79 mCaptivePortalDetector
=
80 do_GetService("@mozilla.org/toolkit/captive-detector;1", &rv
);
82 LOG(("Unable to get a captive portal detector\n"));
87 LOG(("CaptivePortalService::PerformCheck - Calling CheckCaptivePortal\n"));
88 mRequestInProgress
= true;
89 mCaptivePortalDetector
->CheckCaptivePortal(kInterfaceName
, this);
93 nsresult
CaptivePortalService::RearmTimer() {
94 LOG(("CaptivePortalService::RearmTimer\n"));
95 // Start a timer to recheck
96 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default
);
101 // If we have successfully determined the state, and we have never detected
102 // a captive portal, we don't need to keep polling, but will rely on events
103 // to trigger detection.
104 if (mState
== NOT_CAPTIVE
) {
109 mTimer
= NS_NewTimer();
112 if (mTimer
&& mDelay
> 0) {
113 LOG(("CaptivePortalService - Reloading timer with delay %u\n", mDelay
));
114 return mTimer
->InitWithCallback(this, mDelay
, nsITimer::TYPE_ONE_SHOT
);
120 nsresult
CaptivePortalService::Initialize() {
126 // Only the main process service should actually do anything. The service in
127 // the content process only mirrors the CP state in the main process.
128 if (XRE_GetProcessType() != GeckoProcessType_Default
) {
132 nsCOMPtr
<nsIObserverService
> observerService
=
133 mozilla::services::GetObserverService();
134 if (observerService
) {
135 observerService
->AddObserver(this, kOpenCaptivePortalLoginEvent
, true);
136 observerService
->AddObserver(this, kAbortCaptivePortalLoginEvent
, true);
137 observerService
->AddObserver(this, kCaptivePortalLoginSuccessEvent
, true);
140 LOG(("Initialized CaptivePortalService\n"));
144 nsresult
CaptivePortalService::Start() {
146 return NS_ERROR_NOT_INITIALIZED
;
149 if (xpc::AreNonLocalConnectionsDisabled() &&
150 !Preferences::GetBool("network.captive-portal-service.testMode", false)) {
151 return NS_ERROR_NOT_AVAILABLE
;
154 if (XRE_GetProcessType() != GeckoProcessType_Default
) {
155 // Doesn't do anything if called in the content process.
163 MOZ_ASSERT(mState
== UNKNOWN
, "Initial state should be UNKNOWN");
165 mEverBeenCaptive
= false;
167 // Get the delay prefs
168 Preferences::GetUint("network.captive-portal-service.minInterval",
170 Preferences::GetUint("network.captive-portal-service.maxInterval",
172 Preferences::GetFloat("network.captive-portal-service.backoffFactor",
175 LOG(("CaptivePortalService::Start min:%u max:%u backoff:%.2f\n", mMinInterval
,
176 mMaxInterval
, mBackoffFactor
));
179 mDelay
= mMinInterval
;
181 // When Start is called, perform a check immediately
187 nsresult
CaptivePortalService::Stop() {
188 LOG(("CaptivePortalService::Stop\n"));
190 if (XRE_GetProcessType() != GeckoProcessType_Default
) {
191 // Doesn't do anything when called in the content process.
203 mRequestInProgress
= false;
205 if (mCaptivePortalDetector
) {
206 mCaptivePortalDetector
->Abort(kInterfaceName
);
208 mCaptivePortalDetector
= nullptr;
210 // Clear the state in case anyone queries the state while detection is off.
215 void CaptivePortalService::SetStateInChild(int32_t aState
) {
216 // This should only be called in the content process, from ContentChild.cpp
217 // in order to mirror the captive portal state set in the chrome process.
218 MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default
);
221 mLastChecked
= TimeStamp::Now();
224 //-----------------------------------------------------------------------------
225 // CaptivePortalService::nsICaptivePortalService
226 //-----------------------------------------------------------------------------
229 CaptivePortalService::GetState(int32_t *aState
) {
235 CaptivePortalService::RecheckCaptivePortal() {
236 LOG(("CaptivePortalService::RecheckCaptivePortal\n"));
238 if (XRE_GetProcessType() != GeckoProcessType_Default
) {
239 // Doesn't do anything if called in the content process.
243 // This is called for user activity. We need to reset the slack count,
244 // so the checks continue to be quite frequent.
246 mDelay
= mMinInterval
;
254 CaptivePortalService::GetLastChecked(uint64_t *aLastChecked
) {
255 double duration
= (TimeStamp::Now() - mLastChecked
).ToMilliseconds();
256 *aLastChecked
= static_cast<uint64_t>(duration
);
260 //-----------------------------------------------------------------------------
261 // CaptivePortalService::nsITimer
262 // This callback gets called every mDelay miliseconds
263 // It issues a checkCaptivePortal operation if one isn't already in progress
264 //-----------------------------------------------------------------------------
266 CaptivePortalService::Notify(nsITimer
*aTimer
) {
267 LOG(("CaptivePortalService::Notify\n"));
268 MOZ_ASSERT(aTimer
== mTimer
);
269 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default
);
273 // This is needed because we don't want to always make requests very often.
274 // Every 10 checks, we the delay is increased mBackoffFactor times
275 // to a maximum delay of mMaxInterval
277 if (mSlackCount
% 10 == 0) {
278 mDelay
= mDelay
* mBackoffFactor
;
280 if (mDelay
> mMaxInterval
) {
281 mDelay
= mMaxInterval
;
284 // Note - if mDelay is 0, the timer will not be rearmed.
290 //-----------------------------------------------------------------------------
291 // CaptivePortalService::nsINamed
292 //-----------------------------------------------------------------------------
295 CaptivePortalService::GetName(nsACString
&aName
) {
296 aName
.AssignLiteral("CaptivePortalService");
300 //-----------------------------------------------------------------------------
301 // CaptivePortalService::nsIObserver
302 //-----------------------------------------------------------------------------
304 CaptivePortalService::Observe(nsISupports
*aSubject
, const char *aTopic
,
305 const char16_t
*aData
) {
306 if (XRE_GetProcessType() != GeckoProcessType_Default
) {
307 // Doesn't do anything if called in the content process.
311 LOG(("CaptivePortalService::Observe() topic=%s\n", aTopic
));
312 if (!strcmp(aTopic
, kOpenCaptivePortalLoginEvent
)) {
313 // A redirect or altered content has been detected.
314 // The user needs to log in. We are in a captive portal.
315 mState
= LOCKED_PORTAL
;
316 mLastChecked
= TimeStamp::Now();
317 mEverBeenCaptive
= true;
318 } else if (!strcmp(aTopic
, kCaptivePortalLoginSuccessEvent
)) {
319 // The user has successfully logged in. We have connectivity.
320 mState
= UNLOCKED_PORTAL
;
321 mLastChecked
= TimeStamp::Now();
323 mDelay
= mMinInterval
;
326 } else if (!strcmp(aTopic
, kAbortCaptivePortalLoginEvent
)) {
327 // The login has been aborted
329 mLastChecked
= TimeStamp::Now();
333 // Send notification so that the captive portal state is mirrored in the
335 nsCOMPtr
<nsIObserverService
> observerService
= services::GetObserverService();
336 if (observerService
) {
337 nsCOMPtr
<nsICaptivePortalService
> cps(this);
338 observerService
->NotifyObservers(cps
, NS_IPC_CAPTIVE_PORTAL_SET_STATE
,
345 void CaptivePortalService::NotifyConnectivityAvailable(bool aCaptive
) {
346 nsCOMPtr
<nsIObserverService
> observerService
= services::GetObserverService();
347 if (observerService
) {
348 nsCOMPtr
<nsICaptivePortalService
> cps(this);
349 observerService
->NotifyObservers(cps
, NS_CAPTIVE_PORTAL_CONNECTIVITY
,
350 aCaptive
? u
"captive" : u
"clear");
354 //-----------------------------------------------------------------------------
355 // CaptivePortalService::nsICaptivePortalCallback
356 //-----------------------------------------------------------------------------
358 CaptivePortalService::Prepare() {
359 LOG(("CaptivePortalService::Prepare\n"));
360 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default
);
361 // XXX: Finish preparation shouldn't be called until dns and routing is
363 if (mCaptivePortalDetector
) {
364 mCaptivePortalDetector
->FinishPreparation(kInterfaceName
);
370 CaptivePortalService::Complete(bool success
) {
371 LOG(("CaptivePortalService::Complete(success=%d) mState=%d\n", success
,
373 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default
);
374 mLastChecked
= TimeStamp::Now();
376 // Note: this callback gets called when:
377 // 1. the request is completed, and content is valid (success == true)
378 // 2. when the request is aborted or times out (success == false)
381 if (mEverBeenCaptive
) {
382 mState
= UNLOCKED_PORTAL
;
383 NotifyConnectivityAvailable(true);
385 mState
= NOT_CAPTIVE
;
386 NotifyConnectivityAvailable(false);
390 mRequestInProgress
= false;
395 } // namespace mozilla