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/AppShutdown.h"
7 #include "mozilla/ClearOnShutdown.h"
8 #include "mozilla/Services.h"
9 #include "mozilla/Preferences.h"
10 #include "nsIObserverService.h"
11 #include "nsServiceManagerUtils.h"
12 #include "nsXULAppAPI.h"
13 #include "xpcpublic.h"
14 #include "xpcprivate.h"
16 static constexpr auto kInterfaceName
= u
"captive-portal-inteface"_ns
;
18 static const char kOpenCaptivePortalLoginEvent
[] = "captive-portal-login";
19 static const char kAbortCaptivePortalLoginEvent
[] =
20 "captive-portal-login-abort";
21 static const char kCaptivePortalLoginSuccessEvent
[] =
22 "captive-portal-login-success";
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() {
49 mLastChecked
= TimeStamp::Now();
52 CaptivePortalService::~CaptivePortalService() {
53 LOG(("CaptivePortalService::~CaptivePortalService isParentProcess:%d\n",
54 XRE_GetProcessType() == GeckoProcessType_Default
));
57 nsresult
CaptivePortalService::PerformCheck() {
59 ("CaptivePortalService::PerformCheck mRequestInProgress:%d "
60 "mInitialized:%d mStarted:%d\n",
61 mRequestInProgress
, mInitialized
, mStarted
));
62 // Don't issue another request if last one didn't complete
63 if (mRequestInProgress
|| !mInitialized
|| !mStarted
) {
66 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed
)) {
67 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN
;
69 // Instantiating CaptiveDetect.sys.mjs before the JS engine is ready will
70 // lead to a crash (see bug 1800603)
71 // We can remove this restriction when we rewrite the detector in
72 // C++ or rust (bug 1809886).
73 if (!XPCJSRuntime::Get()) {
74 return NS_ERROR_NOT_INITIALIZED
;
76 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default
);
78 if (!mCaptivePortalDetector
) {
79 mCaptivePortalDetector
=
80 do_CreateInstance("@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 (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed
)) {
103 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN
;
106 // If we have successfully determined the state, and we have never detected
107 // a captive portal, we don't need to keep polling, but will rely on events
108 // to trigger detection.
109 if (mState
== NOT_CAPTIVE
) {
114 mTimer
= NS_NewTimer();
117 if (mTimer
&& mDelay
> 0) {
118 LOG(("CaptivePortalService - Reloading timer with delay %u\n", mDelay
));
119 return mTimer
->InitWithCallback(this, mDelay
, nsITimer::TYPE_ONE_SHOT
);
125 nsresult
CaptivePortalService::Initialize() {
131 // Only the main process service should actually do anything. The service in
132 // the content process only mirrors the CP state in the main process.
133 if (XRE_GetProcessType() != GeckoProcessType_Default
) {
137 nsCOMPtr
<nsIObserverService
> observerService
=
138 mozilla::services::GetObserverService();
139 if (observerService
) {
140 observerService
->AddObserver(this, kOpenCaptivePortalLoginEvent
, true);
141 observerService
->AddObserver(this, kAbortCaptivePortalLoginEvent
, true);
142 observerService
->AddObserver(this, kCaptivePortalLoginSuccessEvent
, true);
143 observerService
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, true);
146 LOG(("Initialized CaptivePortalService\n"));
150 nsresult
CaptivePortalService::Start() {
152 return NS_ERROR_NOT_INITIALIZED
;
155 if (xpc::AreNonLocalConnectionsDisabled() &&
156 !Preferences::GetBool("network.captive-portal-service.testMode", false)) {
157 return NS_ERROR_NOT_AVAILABLE
;
160 if (XRE_GetProcessType() != GeckoProcessType_Default
) {
161 // Doesn't do anything if called in the content process.
169 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed
)) {
170 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN
;
173 MOZ_ASSERT(mState
== UNKNOWN
, "Initial state should be UNKNOWN");
175 mEverBeenCaptive
= false;
177 // Get the delay prefs
178 Preferences::GetUint("network.captive-portal-service.minInterval",
180 Preferences::GetUint("network.captive-portal-service.maxInterval",
182 Preferences::GetFloat("network.captive-portal-service.backoffFactor",
185 LOG(("CaptivePortalService::Start min:%u max:%u backoff:%.2f\n", mMinInterval
,
186 mMaxInterval
, mBackoffFactor
));
189 mDelay
= mMinInterval
;
191 // When Start is called, perform a check immediately
197 nsresult
CaptivePortalService::Stop() {
198 LOG(("CaptivePortalService::Stop\n"));
200 if (XRE_GetProcessType() != GeckoProcessType_Default
) {
201 // Doesn't do anything when called in the content process.
213 mRequestInProgress
= false;
215 mEverBeenCaptive
= false;
216 if (mCaptivePortalDetector
) {
217 mCaptivePortalDetector
->Abort(kInterfaceName
);
219 mCaptivePortalDetector
= nullptr;
221 // Clear the state in case anyone queries the state while detection is off.
226 void CaptivePortalService::SetStateInChild(int32_t aState
) {
227 // This should only be called in the content process, from ContentChild.cpp
228 // in order to mirror the captive portal state set in the chrome process.
229 MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default
);
232 mLastChecked
= TimeStamp::Now();
235 //-----------------------------------------------------------------------------
236 // CaptivePortalService::nsICaptivePortalService
237 //-----------------------------------------------------------------------------
240 CaptivePortalService::GetState(int32_t* aState
) {
246 CaptivePortalService::RecheckCaptivePortal() {
247 LOG(("CaptivePortalService::RecheckCaptivePortal\n"));
249 if (XRE_GetProcessType() != GeckoProcessType_Default
) {
250 // Doesn't do anything if called in the content process.
254 // This is called for user activity. We need to reset the slack count,
255 // so the checks continue to be quite frequent.
257 mDelay
= mMinInterval
;
265 CaptivePortalService::GetLastChecked(uint64_t* aLastChecked
) {
266 double duration
= (TimeStamp::Now() - mLastChecked
).ToMilliseconds();
267 *aLastChecked
= static_cast<uint64_t>(duration
);
271 //-----------------------------------------------------------------------------
272 // CaptivePortalService::nsITimer
273 // This callback gets called every mDelay miliseconds
274 // It issues a checkCaptivePortal operation if one isn't already in progress
275 //-----------------------------------------------------------------------------
277 CaptivePortalService::Notify(nsITimer
* aTimer
) {
278 LOG(("CaptivePortalService::Notify\n"));
279 MOZ_ASSERT(aTimer
== mTimer
);
280 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default
);
284 // This is needed because we don't want to always make requests very often.
285 // Every 10 checks, we the delay is increased mBackoffFactor times
286 // to a maximum delay of mMaxInterval
288 if (mSlackCount
% 10 == 0) {
289 mDelay
= mDelay
* mBackoffFactor
;
291 if (mDelay
> mMaxInterval
) {
292 mDelay
= mMaxInterval
;
295 // Note - if mDelay is 0, the timer will not be rearmed.
301 //-----------------------------------------------------------------------------
302 // CaptivePortalService::nsINamed
303 //-----------------------------------------------------------------------------
306 CaptivePortalService::GetName(nsACString
& aName
) {
307 aName
.AssignLiteral("CaptivePortalService");
311 //-----------------------------------------------------------------------------
312 // CaptivePortalService::nsIObserver
313 //-----------------------------------------------------------------------------
315 CaptivePortalService::Observe(nsISupports
* aSubject
, const char* aTopic
,
316 const char16_t
* aData
) {
317 if (XRE_GetProcessType() != GeckoProcessType_Default
) {
318 // Doesn't do anything if called in the content process.
322 LOG(("CaptivePortalService::Observe() topic=%s\n", aTopic
));
323 if (!strcmp(aTopic
, kOpenCaptivePortalLoginEvent
)) {
324 // A redirect or altered content has been detected.
325 // The user needs to log in. We are in a captive portal.
326 StateTransition(LOCKED_PORTAL
);
327 mLastChecked
= TimeStamp::Now();
328 mEverBeenCaptive
= true;
329 } else if (!strcmp(aTopic
, kCaptivePortalLoginSuccessEvent
)) {
330 // The user has successfully logged in. We have connectivity.
331 StateTransition(UNLOCKED_PORTAL
);
332 mLastChecked
= TimeStamp::Now();
334 mDelay
= mMinInterval
;
337 } else if (!strcmp(aTopic
, kAbortCaptivePortalLoginEvent
)) {
338 // The login has been aborted
339 StateTransition(UNKNOWN
);
340 mLastChecked
= TimeStamp::Now();
342 } else if (!strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
)) {
347 // Send notification so that the captive portal state is mirrored in the
349 nsCOMPtr
<nsIObserverService
> observerService
= services::GetObserverService();
350 if (observerService
) {
351 nsCOMPtr
<nsICaptivePortalService
> cps(this);
352 observerService
->NotifyObservers(cps
, NS_IPC_CAPTIVE_PORTAL_SET_STATE
,
359 void CaptivePortalService::NotifyConnectivityAvailable(bool aCaptive
) {
360 nsCOMPtr
<nsIObserverService
> observerService
= services::GetObserverService();
361 if (observerService
) {
362 nsCOMPtr
<nsICaptivePortalService
> cps(this);
363 observerService
->NotifyObservers(cps
, NS_CAPTIVE_PORTAL_CONNECTIVITY
,
364 aCaptive
? u
"captive" : u
"clear");
368 //-----------------------------------------------------------------------------
369 // CaptivePortalService::nsICaptivePortalCallback
370 //-----------------------------------------------------------------------------
372 CaptivePortalService::Prepare() {
373 LOG(("CaptivePortalService::Prepare\n"));
374 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default
);
375 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed
)) {
378 // XXX: Finish preparation shouldn't be called until dns and routing is
380 if (mCaptivePortalDetector
) {
381 mCaptivePortalDetector
->FinishPreparation(kInterfaceName
);
387 CaptivePortalService::Complete(bool success
) {
388 LOG(("CaptivePortalService::Complete(success=%d) mState=%d\n", success
,
390 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default
);
391 mLastChecked
= TimeStamp::Now();
393 // Note: this callback gets called when:
394 // 1. the request is completed, and content is valid (success == true)
395 // 2. when the request is aborted or times out (success == false)
398 if (mEverBeenCaptive
) {
399 StateTransition(UNLOCKED_PORTAL
);
400 NotifyConnectivityAvailable(true);
402 StateTransition(NOT_CAPTIVE
);
403 NotifyConnectivityAvailable(false);
407 mRequestInProgress
= false;
411 void CaptivePortalService::StateTransition(int32_t aNewState
) {
412 int32_t oldState
= mState
;
415 if ((oldState
== UNKNOWN
&& mState
== NOT_CAPTIVE
) ||
416 (oldState
== LOCKED_PORTAL
&& mState
== UNLOCKED_PORTAL
)) {
417 nsCOMPtr
<nsIObserverService
> observerService
=
418 services::GetObserverService();
419 if (observerService
) {
420 nsCOMPtr
<nsICaptivePortalService
> cps(this);
421 observerService
->NotifyObservers(
422 cps
, NS_CAPTIVE_PORTAL_CONNECTIVITY_CHANGED
, nullptr);
428 } // namespace mozilla