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/. */
6 #include "nsProxyRelease.h"
7 #include "nsComponentManagerUtils.h"
8 #include "nsServiceManagerUtils.h"
9 #include "nsThreadUtils.h"
11 #include "nsXPCOMCID.h"
12 #include "nsIObserver.h"
13 #include "nsIObserverService.h"
14 #include "nsWifiMonitor.h"
15 #include "nsWifiAccessPoint.h"
16 #include "nsINetworkLinkService.h"
17 #include "nsQueryObject.h"
20 #include "nsComponentManagerUtils.h"
21 #include "mozilla/DelayedRunnable.h"
22 #include "mozilla/IntegerPrintfMacros.h"
23 #include "mozilla/StaticPrefs_network.h"
24 #include "mozilla/Services.h"
27 # include "WinWifiScanner.h"
30 #if defined(XP_MACOSX)
31 # include "MacWifiScanner.h"
34 #if defined(__DragonFly__) || defined(__FreeBSD__)
35 # include "FreeBsdWifiScanner.h"
38 #if defined(XP_SOLARIS)
39 # include "SolarisWifiScanner.h"
42 #if defined(NECKO_WIFI_DBUS)
43 # include "DbusWifiScanner.h"
46 using namespace mozilla
;
48 LazyLogModule
gWifiMonitorLog("WifiMonitor");
49 #define LOG(args) MOZ_LOG(gWifiMonitorLog, mozilla::LogLevel::Debug, args)
51 NS_IMPL_ISUPPORTS(nsWifiMonitor
, nsIObserver
, nsIWifiMonitor
)
54 static uint64_t sNextPollingIndex
= 1;
56 static uint64_t NextPollingIndex() {
57 MOZ_ASSERT(NS_IsMainThread());
60 // Any non-zero value is valid and we don't care about overflow beyond
61 // that we never want the index to be zero.
62 if (sNextPollingIndex
== 0) {
65 return sNextPollingIndex
;
68 // Should we poll wifi or just check it when our network changes?
69 // We poll when we are on a network where the wifi environment
70 // could reasonably be expected to change much -- so, on mobile.
71 static bool ShouldPollForNetworkType(const char16_t
* aLinkType
) {
72 auto linkTypeU8
= NS_ConvertUTF16toUTF8(aLinkType
);
73 return linkTypeU8
== NS_NETWORK_LINK_TYPE_WIMAX
||
74 linkTypeU8
== NS_NETWORK_LINK_TYPE_MOBILE
||
75 linkTypeU8
== NS_NETWORK_LINK_TYPE_UNKNOWN
;
78 // Enum value version.
79 static bool ShouldPollForNetworkType(uint32_t aLinkType
) {
80 return aLinkType
== nsINetworkLinkService::LINK_TYPE_WIMAX
||
81 aLinkType
== nsINetworkLinkService::LINK_TYPE_MOBILE
||
82 aLinkType
== nsINetworkLinkService::LINK_TYPE_UNKNOWN
;
85 nsWifiMonitor::nsWifiMonitor(UniquePtr
<mozilla::WifiScanner
>&& aScanner
)
86 : mWifiScanner(std::move(aScanner
)) {
87 LOG(("Creating nsWifiMonitor"));
88 MOZ_ASSERT(NS_IsMainThread());
90 nsCOMPtr
<nsIObserverService
> obsSvc
= mozilla::services::GetObserverService();
92 obsSvc
->AddObserver(this, NS_NETWORK_LINK_TOPIC
, false);
93 obsSvc
->AddObserver(this, NS_NETWORK_LINK_TYPE_TOPIC
, false);
94 obsSvc
->AddObserver(this, "xpcom-shutdown", false);
98 nsCOMPtr
<nsINetworkLinkService
> nls
=
99 do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID
, &rv
);
100 if (NS_SUCCEEDED(rv
) && nls
) {
101 uint32_t linkType
= nsINetworkLinkService::LINK_TYPE_UNKNOWN
;
102 rv
= nls
->GetLinkType(&linkType
);
103 if (NS_SUCCEEDED(rv
)) {
104 mShouldPollForCurrentNetwork
= ShouldPollForNetworkType(linkType
);
106 mPollingId
= NextPollingIndex();
107 DispatchScanToBackgroundThread(mPollingId
);
109 LOG(("nsWifiMonitor network type: %u | shouldPoll: %s", linkType
,
110 mShouldPollForCurrentNetwork
? "true" : "false"));
114 nsWifiMonitor::~nsWifiMonitor() { LOG(("Destroying nsWifiMonitor")); }
116 void nsWifiMonitor::Close() {
117 nsCOMPtr
<nsIObserverService
> obsSvc
= mozilla::services::GetObserverService();
119 obsSvc
->RemoveObserver(this, NS_NETWORK_LINK_TOPIC
);
120 obsSvc
->RemoveObserver(this, NS_NETWORK_LINK_TYPE_TOPIC
);
121 obsSvc
->RemoveObserver(this, "xpcom-shutdown");
131 nsWifiMonitor::Observe(nsISupports
* subject
, const char* topic
,
132 const char16_t
* data
) {
133 MOZ_ASSERT(NS_IsMainThread());
135 if (!strcmp(topic
, "xpcom-shutdown")) {
136 // Make sure any wifi-polling stops.
137 LOG(("nsWifiMonitor received shutdown"));
139 } else if (!strcmp(topic
, NS_NETWORK_LINK_TOPIC
)) {
140 // Network connectivity has either been gained, lost, or changed (e.g.
141 // by changing Wifi network). Issue an immediate one-time scan.
142 // If we were polling, keep polling.
143 LOG(("nsWifiMonitor %p | mPollingId %" PRIu64
144 " | received: " NS_NETWORK_LINK_TOPIC
" with status %s",
145 this, static_cast<uint64_t>(mPollingId
),
146 NS_ConvertUTF16toUTF8(data
).get()));
147 DispatchScanToBackgroundThread(0);
148 } else if (!strcmp(topic
, NS_NETWORK_LINK_TYPE_TOPIC
)) {
149 // Network type has changed (e.g. from wifi to mobile). When on some
150 // network types, we poll wifi. This event does not indicate that a
151 // new scan would be beneficial right now, so we only issue one if
152 // we need to begin polling.
153 // Use IDs to make sure only one task is polling at a time.
154 LOG(("nsWifiMonitor %p | mPollingId %" PRIu64
155 " | received: " NS_NETWORK_LINK_TYPE_TOPIC
" with status %s",
156 this, static_cast<uint64_t>(mPollingId
),
157 NS_ConvertUTF16toUTF8(data
).get()));
159 bool wasPolling
= ShouldPoll();
160 MOZ_ASSERT(wasPolling
|| mPollingId
== 0);
162 mShouldPollForCurrentNetwork
= ShouldPollForNetworkType(data
);
163 if (!wasPolling
&& ShouldPoll()) {
164 // We weren't polling, so start now.
165 mPollingId
= NextPollingIndex();
166 DispatchScanToBackgroundThread(mPollingId
);
167 } else if (!ShouldPoll()) {
168 // Stop polling if we were.
176 NS_IMETHODIMP
nsWifiMonitor::StartWatching(nsIWifiListener
* aListener
,
177 bool aForcePolling
) {
178 LOG(("nsWifiMonitor::StartWatching %p | listener %p | mPollingId %" PRIu64
179 " | aForcePolling %s",
180 this, aListener
, static_cast<uint64_t>(mPollingId
),
181 aForcePolling
? "true" : "false"));
182 MOZ_ASSERT(NS_IsMainThread());
185 return NS_ERROR_NULL_POINTER
;
188 mListeners
.AppendElement(WifiListenerHolder(aListener
, aForcePolling
));
190 // Run a new scan to update the new listener. If we were polling then
191 // stop that polling and start a new polling interval now.
192 MOZ_ASSERT(mPollingId
== 0 || ShouldPoll());
194 ++mNumPollingListeners
;
197 mPollingId
= NextPollingIndex();
199 return DispatchScanToBackgroundThread(mPollingId
);
202 NS_IMETHODIMP
nsWifiMonitor::StopWatching(nsIWifiListener
* aListener
) {
203 LOG(("nsWifiMonitor::StopWatching %p | listener %p | mPollingId %" PRIu64
,
204 this, aListener
, static_cast<uint64_t>(mPollingId
)));
205 MOZ_ASSERT(NS_IsMainThread());
208 return NS_ERROR_NULL_POINTER
;
211 auto idx
= mListeners
.IndexOf(
212 WifiListenerHolder(aListener
), 0,
213 [](const WifiListenerHolder
& elt
, const WifiListenerHolder
& toRemove
) {
214 return toRemove
.mListener
== elt
.mListener
? 0 : 1;
217 if (idx
== nsTArray
<WifiListenerHolder
>::NoIndex
) {
218 return NS_ERROR_INVALID_ARG
;
221 if (mListeners
[idx
].mShouldPoll
) {
222 --mNumPollingListeners
;
225 mListeners
.RemoveElementAt(idx
);
228 // Stop polling (if we were).
229 LOG(("nsWifiMonitor::StopWatching clearing polling ID"));
236 nsresult
nsWifiMonitor::DispatchScanToBackgroundThread(uint64_t aPollingId
,
238 RefPtr
<Runnable
> runnable
= NewRunnableMethod
<uint64_t>(
239 "WifiScannerThread", this, &nsWifiMonitor::Scan
, aPollingId
);
242 MOZ_ASSERT(NS_IsMainThread());
245 nsIThreadManager::ThreadCreationOptions options
= {};
247 // If this ASSERT fails, we've increased our default stack size and
248 // may no longer need to special-case the stack size on macOS.
249 static_assert(kMacOSWifiMonitorStackSize
>
250 nsIThreadManager::DEFAULT_STACK_SIZE
);
252 // Mac needs a stack size larger than the default for CoreWLAN.
253 nsIThreadManager::ThreadCreationOptions options
= {
254 .stackSize
= kMacOSWifiMonitorStackSize
};
257 nsresult rv
= NS_NewNamedThread("Wifi Monitor", getter_AddRefs(mThread
),
259 NS_ENSURE_SUCCESS(rv
, rv
);
263 return mThread
->DelayedDispatch(runnable
.forget(), aWaitMs
);
266 return mThread
->Dispatch(runnable
.forget());
269 bool nsWifiMonitor::IsBackgroundThread() {
270 return NS_GetCurrentThread() == mThread
;
273 void nsWifiMonitor::Scan(uint64_t aPollingId
) {
274 MOZ_ASSERT(IsBackgroundThread());
275 LOG(("nsWifiMonitor::Scan aPollingId: %" PRIu64
" | mPollingId: %" PRIu64
,
276 aPollingId
, static_cast<uint64_t>(mPollingId
)));
278 // If we are using a stale polling ID then stop. If this request to
279 // Scan is not for polling (aPollingId is 0) then always allow it.
280 if (aPollingId
&& mPollingId
!= aPollingId
) {
281 LOG(("nsWifiMonitor::Scan stopping polling"));
285 LOG(("nsWifiMonitor::Scan starting DoScan with id: %" PRIu64
, aPollingId
));
286 nsresult rv
= DoScan();
287 LOG(("nsWifiMonitor::Scan DoScan complete | rv = %d",
288 static_cast<uint32_t>(rv
)));
291 rv
= NS_DispatchToMainThread(NewRunnableMethod
<nsresult
>(
292 "PassErrorToWifiListeners", this,
293 &nsWifiMonitor::PassErrorToWifiListeners
, rv
));
294 MOZ_ASSERT(NS_SUCCEEDED(rv
));
297 // If we are polling then we re-issue Scan after a delay.
298 // We re-check the polling IDs since mPollingId may have changed.
299 if (aPollingId
&& aPollingId
== mPollingId
) {
300 uint32_t periodMs
= StaticPrefs::network_wifi_scanning_period();
302 LOG(("nsWifiMonitor::Scan requesting future scan with id: %" PRIu64
304 aPollingId
, periodMs
));
305 DispatchScanToBackgroundThread(aPollingId
, periodMs
);
307 // Polling for wifi-scans is disabled.
312 LOG(("nsWifiMonitor::Scan complete"));
315 nsresult
nsWifiMonitor::DoScan() {
316 MOZ_ASSERT(IsBackgroundThread());
319 LOG(("Constructing WifiScanner"));
320 mWifiScanner
= MakeUnique
<mozilla::WifiScannerImpl
>();
322 MOZ_ASSERT(mWifiScanner
);
324 LOG(("Scanning Wifi for access points"));
325 nsTArray
<RefPtr
<nsIWifiAccessPoint
>> accessPoints
;
326 nsresult rv
= mWifiScanner
->GetAccessPointsFromWLAN(accessPoints
);
331 LOG(("Sorting wifi access points"));
332 accessPoints
.Sort([](const RefPtr
<nsIWifiAccessPoint
>& ia
,
333 const RefPtr
<nsIWifiAccessPoint
>& ib
) {
334 const auto& a
= static_cast<const nsWifiAccessPoint
&>(*ia
);
335 const auto& b
= static_cast<const nsWifiAccessPoint
&>(*ib
);
339 // Sorted compare to see if access point list has changed.
340 LOG(("Checking for new access points"));
341 bool accessPointsChanged
=
342 accessPoints
.Length() != mLastAccessPoints
.Length();
343 if (!accessPointsChanged
) {
344 auto itAp
= accessPoints
.begin();
345 auto itLastAp
= mLastAccessPoints
.begin();
346 while (itAp
!= accessPoints
.end()) {
347 const auto& a
= static_cast<const nsWifiAccessPoint
&>(**itAp
);
348 const auto& b
= static_cast<const nsWifiAccessPoint
&>(**itLastAp
);
350 accessPointsChanged
= true;
358 mLastAccessPoints
= std::move(accessPoints
);
360 LOG(("Sending Wifi access points to the main thread"));
361 auto* mainThread
= GetMainThreadSerialEventTarget();
363 return NS_ERROR_UNEXPECTED
;
366 return NS_DispatchToMainThread(
367 NewRunnableMethod
<nsTArray
<RefPtr
<nsIWifiAccessPoint
>>, bool>(
368 "CallWifiListeners", this, &nsWifiMonitor::CallWifiListeners
,
369 mLastAccessPoints
.Clone(), accessPointsChanged
));
372 nsresult
nsWifiMonitor::CallWifiListeners(
373 const nsTArray
<RefPtr
<nsIWifiAccessPoint
>>& aAccessPoints
,
374 bool aAccessPointsChanged
) {
375 MOZ_ASSERT(NS_IsMainThread());
376 LOG(("Sending wifi access points to the listeners"));
377 for (auto& listener
: mListeners
) {
378 if (!listener
.mHasSentData
|| aAccessPointsChanged
) {
379 listener
.mHasSentData
= true;
380 listener
.mListener
->OnChange(aAccessPoints
);
386 nsresult
nsWifiMonitor::PassErrorToWifiListeners(nsresult rv
) {
387 MOZ_ASSERT(NS_IsMainThread());
388 LOG(("About to send error to the wifi listeners"));
389 for (const auto& listener
: mListeners
) {
390 listener
.mListener
->OnError(rv
);