Bug 1869043 assert that graph set access is main thread only r=padenot
[gecko.git] / netwerk / wifi / nsWifiMonitor.cpp
blob5a7be15d162cfcafafd8b566d688f046a5074a71
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 "nsCOMPtr.h"
6 #include "nsProxyRelease.h"
7 #include "nsComponentManagerUtils.h"
8 #include "nsServiceManagerUtils.h"
9 #include "nsThreadUtils.h"
10 #include "nsXPCOM.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"
18 #include "nsNetCID.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"
26 #if defined(XP_WIN)
27 # include "WinWifiScanner.h"
28 #endif
30 #if defined(XP_MACOSX)
31 # include "MacWifiScanner.h"
32 #endif
34 #if defined(__DragonFly__) || defined(__FreeBSD__)
35 # include "FreeBsdWifiScanner.h"
36 #endif
38 #if defined(XP_SOLARIS)
39 # include "SolarisWifiScanner.h"
40 #endif
42 #if defined(NECKO_WIFI_DBUS)
43 # include "DbusWifiScanner.h"
44 #endif
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)
53 // Main thread only.
54 static uint64_t sNextPollingIndex = 1;
56 static uint64_t NextPollingIndex() {
57 MOZ_ASSERT(NS_IsMainThread());
58 ++sNextPollingIndex;
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) {
63 ++sNextPollingIndex;
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();
91 if (obsSvc) {
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);
97 nsresult rv;
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);
105 if (ShouldPoll()) {
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();
118 if (obsSvc) {
119 obsSvc->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
120 obsSvc->RemoveObserver(this, NS_NETWORK_LINK_TYPE_TOPIC);
121 obsSvc->RemoveObserver(this, "xpcom-shutdown");
124 mPollingId = 0;
125 if (mThread) {
126 mThread->Shutdown();
130 NS_IMETHODIMP
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"));
138 Close();
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.
169 mPollingId = 0;
173 return NS_OK;
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());
184 if (!aListener) {
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());
193 if (aForcePolling) {
194 ++mNumPollingListeners;
196 if (ShouldPoll()) {
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());
207 if (!aListener) {
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);
227 if (!ShouldPoll()) {
228 // Stop polling (if we were).
229 LOG(("nsWifiMonitor::StopWatching clearing polling ID"));
230 mPollingId = 0;
233 return NS_OK;
236 nsresult nsWifiMonitor::DispatchScanToBackgroundThread(uint64_t aPollingId,
237 uint32_t aWaitMs) {
238 RefPtr<Runnable> runnable = NewRunnableMethod<uint64_t>(
239 "WifiScannerThread", this, &nsWifiMonitor::Scan, aPollingId);
241 if (!mThread) {
242 MOZ_ASSERT(NS_IsMainThread());
244 #ifndef XP_MACOSX
245 nsIThreadManager::ThreadCreationOptions options = {};
246 #else
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};
255 #endif
257 nsresult rv = NS_NewNamedThread("Wifi Monitor", getter_AddRefs(mThread),
258 nullptr, options);
259 NS_ENSURE_SUCCESS(rv, rv);
262 if (aWaitMs) {
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"));
282 return;
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)));
290 if (NS_FAILED(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();
301 if (periodMs) {
302 LOG(("nsWifiMonitor::Scan requesting future scan with id: %" PRIu64
303 " | periodMs: %u",
304 aPollingId, periodMs));
305 DispatchScanToBackgroundThread(aPollingId, periodMs);
306 } else {
307 // Polling for wifi-scans is disabled.
308 mPollingId = 0;
312 LOG(("nsWifiMonitor::Scan complete"));
315 nsresult nsWifiMonitor::DoScan() {
316 MOZ_ASSERT(IsBackgroundThread());
318 if (!mWifiScanner) {
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);
327 if (NS_FAILED(rv)) {
328 return rv;
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);
336 return a.Compare(b);
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);
349 if (a != b) {
350 accessPointsChanged = true;
351 break;
353 ++itAp;
354 ++itLastAp;
358 mLastAccessPoints = std::move(accessPoints);
360 LOG(("Sending Wifi access points to the main thread"));
361 auto* mainThread = GetMainThreadSerialEventTarget();
362 if (!mainThread) {
363 return NS_ERROR_UNEXPECTED;
366 return NS_DispatchToMainThread(
367 NewRunnableMethod<const nsTArray<RefPtr<nsIWifiAccessPoint>>&&, bool>(
368 "CallWifiListeners", this, &nsWifiMonitor::CallWifiListeners,
369 mLastAccessPoints.Clone(), accessPointsChanged));
372 nsresult nsWifiMonitor::CallWifiListeners(
373 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);
383 return NS_OK;
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);
392 return NS_OK;