Bug 1837643 [wpt PR 40475] - [RemoveLegacy] GridTrackList::legacy_track_list_, a...
[gecko.git] / netwerk / base / CaptivePortalService.cpp
blob97a1dde9296ae77ebff3abe3f411f1a96596ee1c
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";
24 namespace mozilla {
25 namespace net {
27 static LazyLogModule gCaptivePortalLog("CaptivePortalService");
28 #undef LOG
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;
37 // static
38 already_AddRefed<nsICaptivePortalService> CaptivePortalService::GetSingleton() {
39 if (gCPService) {
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() {
58 LOG(
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) {
64 return NS_OK;
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);
77 nsresult rv;
78 if (!mCaptivePortalDetector) {
79 mCaptivePortalDetector =
80 do_CreateInstance("@mozilla.org/toolkit/captive-detector;1", &rv);
81 if (NS_FAILED(rv)) {
82 LOG(("Unable to get a captive portal detector\n"));
83 return rv;
87 LOG(("CaptivePortalService::PerformCheck - Calling CheckCaptivePortal\n"));
88 mRequestInProgress = true;
89 mCaptivePortalDetector->CheckCaptivePortal(kInterfaceName, this);
90 return NS_OK;
93 nsresult CaptivePortalService::RearmTimer() {
94 LOG(("CaptivePortalService::RearmTimer\n"));
95 // Start a timer to recheck
96 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
97 if (mTimer) {
98 mTimer->Cancel();
101 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
102 mTimer = nullptr;
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) {
110 return NS_OK;
113 if (!mTimer) {
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);
122 return NS_OK;
125 nsresult CaptivePortalService::Initialize() {
126 if (mInitialized) {
127 return NS_OK;
129 mInitialized = true;
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) {
134 return NS_OK;
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"));
147 return NS_OK;
150 nsresult CaptivePortalService::Start() {
151 if (!mInitialized) {
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.
162 return NS_OK;
165 if (mStarted) {
166 return NS_OK;
169 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
170 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
173 MOZ_ASSERT(mState == UNKNOWN, "Initial state should be UNKNOWN");
174 mStarted = true;
175 mEverBeenCaptive = false;
177 // Get the delay prefs
178 Preferences::GetUint("network.captive-portal-service.minInterval",
179 &mMinInterval);
180 Preferences::GetUint("network.captive-portal-service.maxInterval",
181 &mMaxInterval);
182 Preferences::GetFloat("network.captive-portal-service.backoffFactor",
183 &mBackoffFactor);
185 LOG(("CaptivePortalService::Start min:%u max:%u backoff:%.2f\n", mMinInterval,
186 mMaxInterval, mBackoffFactor));
188 mSlackCount = 0;
189 mDelay = mMinInterval;
191 // When Start is called, perform a check immediately
192 PerformCheck();
193 RearmTimer();
194 return NS_OK;
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.
202 return NS_OK;
205 if (!mStarted) {
206 return NS_OK;
209 if (mTimer) {
210 mTimer->Cancel();
212 mTimer = nullptr;
213 mRequestInProgress = false;
214 mStarted = 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.
222 mState = UNKNOWN;
223 return NS_OK;
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);
231 mState = aState;
232 mLastChecked = TimeStamp::Now();
235 //-----------------------------------------------------------------------------
236 // CaptivePortalService::nsICaptivePortalService
237 //-----------------------------------------------------------------------------
239 NS_IMETHODIMP
240 CaptivePortalService::GetState(int32_t* aState) {
241 *aState = mState;
242 return NS_OK;
245 NS_IMETHODIMP
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.
251 return NS_OK;
254 // This is called for user activity. We need to reset the slack count,
255 // so the checks continue to be quite frequent.
256 mSlackCount = 0;
257 mDelay = mMinInterval;
259 PerformCheck();
260 RearmTimer();
261 return NS_OK;
264 NS_IMETHODIMP
265 CaptivePortalService::GetLastChecked(uint64_t* aLastChecked) {
266 double duration = (TimeStamp::Now() - mLastChecked).ToMilliseconds();
267 *aLastChecked = static_cast<uint64_t>(duration);
268 return NS_OK;
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 //-----------------------------------------------------------------------------
276 NS_IMETHODIMP
277 CaptivePortalService::Notify(nsITimer* aTimer) {
278 LOG(("CaptivePortalService::Notify\n"));
279 MOZ_ASSERT(aTimer == mTimer);
280 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
282 PerformCheck();
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
287 mSlackCount++;
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.
296 RearmTimer();
298 return NS_OK;
301 //-----------------------------------------------------------------------------
302 // CaptivePortalService::nsINamed
303 //-----------------------------------------------------------------------------
305 NS_IMETHODIMP
306 CaptivePortalService::GetName(nsACString& aName) {
307 aName.AssignLiteral("CaptivePortalService");
308 return NS_OK;
311 //-----------------------------------------------------------------------------
312 // CaptivePortalService::nsIObserver
313 //-----------------------------------------------------------------------------
314 NS_IMETHODIMP
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.
319 return NS_OK;
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();
333 mSlackCount = 0;
334 mDelay = mMinInterval;
336 RearmTimer();
337 } else if (!strcmp(aTopic, kAbortCaptivePortalLoginEvent)) {
338 // The login has been aborted
339 StateTransition(UNKNOWN);
340 mLastChecked = TimeStamp::Now();
341 mSlackCount = 0;
342 } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
343 Stop();
344 return NS_OK;
347 // Send notification so that the captive portal state is mirrored in the
348 // content process.
349 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
350 if (observerService) {
351 nsCOMPtr<nsICaptivePortalService> cps(this);
352 observerService->NotifyObservers(cps, NS_IPC_CAPTIVE_PORTAL_SET_STATE,
353 nullptr);
356 return NS_OK;
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 //-----------------------------------------------------------------------------
371 NS_IMETHODIMP
372 CaptivePortalService::Prepare() {
373 LOG(("CaptivePortalService::Prepare\n"));
374 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
375 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
376 return NS_OK;
378 // XXX: Finish preparation shouldn't be called until dns and routing is
379 // available.
380 if (mCaptivePortalDetector) {
381 mCaptivePortalDetector->FinishPreparation(kInterfaceName);
383 return NS_OK;
386 NS_IMETHODIMP
387 CaptivePortalService::Complete(bool success) {
388 LOG(("CaptivePortalService::Complete(success=%d) mState=%d\n", success,
389 mState));
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)
397 if (success) {
398 if (mEverBeenCaptive) {
399 StateTransition(UNLOCKED_PORTAL);
400 NotifyConnectivityAvailable(true);
401 } else {
402 StateTransition(NOT_CAPTIVE);
403 NotifyConnectivityAvailable(false);
407 mRequestInProgress = false;
408 return NS_OK;
411 void CaptivePortalService::StateTransition(int32_t aNewState) {
412 int32_t oldState = mState;
413 mState = aNewState;
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);
427 } // namespace net
428 } // namespace mozilla