nsWifiMonitor.cpp (13761B)
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/. */ 4 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" 19 20 #include "nsComponentManagerUtils.h" 21 #include "mozilla/Components.h" 22 #include "mozilla/DelayedRunnable.h" 23 #include "mozilla/IntegerPrintfMacros.h" 24 #include "mozilla/StaticPrefs_network.h" 25 #include "mozilla/Services.h" 26 27 #if defined(XP_WIN) 28 # include "WinWifiScanner.h" 29 #endif 30 31 #if defined(XP_MACOSX) 32 # include "MacWifiScanner.h" 33 #endif 34 35 #if defined(__DragonFly__) || defined(__FreeBSD__) 36 # include "FreeBsdWifiScanner.h" 37 #endif 38 39 #if defined(XP_SOLARIS) 40 # include "SolarisWifiScanner.h" 41 #endif 42 43 #if defined(NECKO_WIFI_DBUS) 44 # include "DbusWifiScanner.h" 45 #endif 46 47 using namespace mozilla; 48 49 LazyLogModule gWifiMonitorLog("WifiMonitor"); 50 #define LOG(args) MOZ_LOG(gWifiMonitorLog, mozilla::LogLevel::Debug, args) 51 52 NS_IMPL_ISUPPORTS(nsWifiMonitor, nsIObserver, nsIWifiMonitor) 53 54 // Main thread only. 55 static uint64_t sNextPollingIndex = 1; 56 57 static uint64_t NextPollingIndex() { 58 MOZ_ASSERT(NS_IsMainThread()); 59 ++sNextPollingIndex; 60 61 // Any non-zero value is valid and we don't care about overflow beyond 62 // that we never want the index to be zero. 63 if (sNextPollingIndex == 0) { 64 ++sNextPollingIndex; 65 } 66 return sNextPollingIndex; 67 } 68 69 // Should we poll wifi or just check it when our network changes? 70 // We poll when we are on a network where the wifi environment 71 // could reasonably be expected to change much -- so, on mobile. 72 static bool ShouldPollForNetworkType(const char16_t* aLinkType) { 73 auto linkTypeU8 = NS_ConvertUTF16toUTF8(aLinkType); 74 return linkTypeU8 == NS_NETWORK_LINK_TYPE_WIMAX || 75 linkTypeU8 == NS_NETWORK_LINK_TYPE_MOBILE || 76 linkTypeU8 == NS_NETWORK_LINK_TYPE_UNKNOWN; 77 } 78 79 // Enum value version. 80 static bool ShouldPollForNetworkType(uint32_t aLinkType) { 81 return aLinkType == nsINetworkLinkService::LINK_TYPE_WIMAX || 82 aLinkType == nsINetworkLinkService::LINK_TYPE_MOBILE || 83 aLinkType == nsINetworkLinkService::LINK_TYPE_UNKNOWN; 84 } 85 86 nsWifiMonitor::nsWifiMonitor(UniquePtr<mozilla::WifiScanner>&& aScanner) 87 : mWifiScanner(std::move(aScanner)) { 88 LOG(("Creating nsWifiMonitor")); 89 MOZ_ASSERT(NS_IsMainThread()); 90 91 nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService(); 92 if (obsSvc) { 93 obsSvc->AddObserver(this, NS_NETWORK_LINK_TOPIC, false); 94 obsSvc->AddObserver(this, NS_NETWORK_LINK_TYPE_TOPIC, false); 95 obsSvc->AddObserver(this, "xpcom-shutdown", false); 96 } 97 98 nsresult rv; 99 nsCOMPtr<nsINetworkLinkService> nls; 100 nls = do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv); 101 if (NS_SUCCEEDED(rv) && nls) { 102 uint32_t linkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN; 103 rv = nls->GetLinkType(&linkType); 104 if (NS_SUCCEEDED(rv)) { 105 mShouldPollForCurrentNetwork = ShouldPollForNetworkType(linkType); 106 if (ShouldPoll()) { 107 mPollingId = NextPollingIndex(); 108 DispatchScanToBackgroundThread(mPollingId); 109 } 110 LOG(("nsWifiMonitor network type: %u | shouldPoll: %s", linkType, 111 mShouldPollForCurrentNetwork ? "true" : "false")); 112 } 113 } 114 } 115 nsWifiMonitor::~nsWifiMonitor() { LOG(("Destroying nsWifiMonitor")); } 116 117 void nsWifiMonitor::Close() { 118 nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService(); 119 if (obsSvc) { 120 obsSvc->RemoveObserver(this, NS_NETWORK_LINK_TOPIC); 121 obsSvc->RemoveObserver(this, NS_NETWORK_LINK_TYPE_TOPIC); 122 obsSvc->RemoveObserver(this, "xpcom-shutdown"); 123 } 124 125 mPollingId = 0; 126 if (mThread) { 127 mThread->Shutdown(); 128 } 129 } 130 131 NS_IMETHODIMP 132 nsWifiMonitor::Observe(nsISupports* subject, const char* topic, 133 const char16_t* data) { 134 MOZ_ASSERT(NS_IsMainThread()); 135 136 if (!strcmp(topic, "xpcom-shutdown")) { 137 // Make sure any wifi-polling stops. 138 LOG(("nsWifiMonitor received shutdown")); 139 Close(); 140 } else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) { 141 // Network connectivity has either been gained, lost, or changed (e.g. 142 // by changing Wifi network). Issue an immediate one-time scan. 143 // If we were polling, keep polling. 144 LOG(("nsWifiMonitor %p | mPollingId %" PRIu64 145 " | received: " NS_NETWORK_LINK_TOPIC " with status %s", 146 this, static_cast<uint64_t>(mPollingId), 147 NS_ConvertUTF16toUTF8(data).get())); 148 DispatchScanToBackgroundThread(0); 149 } else if (!strcmp(topic, NS_NETWORK_LINK_TYPE_TOPIC)) { 150 // Network type has changed (e.g. from wifi to mobile). When on some 151 // network types, we poll wifi. This event does not indicate that a 152 // new scan would be beneficial right now, so we only issue one if 153 // we need to begin polling. 154 // Use IDs to make sure only one task is polling at a time. 155 LOG(("nsWifiMonitor %p | mPollingId %" PRIu64 156 " | received: " NS_NETWORK_LINK_TYPE_TOPIC " with status %s", 157 this, static_cast<uint64_t>(mPollingId), 158 NS_ConvertUTF16toUTF8(data).get())); 159 160 bool wasPolling = ShouldPoll(); 161 MOZ_ASSERT(wasPolling || mPollingId == 0); 162 163 mShouldPollForCurrentNetwork = ShouldPollForNetworkType(data); 164 if (!wasPolling && ShouldPoll()) { 165 // We weren't polling, so start now. 166 mPollingId = NextPollingIndex(); 167 DispatchScanToBackgroundThread(mPollingId); 168 } else if (!ShouldPoll()) { 169 // Stop polling if we were. 170 mPollingId = 0; 171 } 172 } 173 174 return NS_OK; 175 } 176 177 void nsWifiMonitor::EnsureWifiScanner() { 178 if (mWifiScanner) { 179 return; 180 } 181 182 LOG(("Constructing WifiScanner")); 183 mWifiScanner = MakeUnique<mozilla::WifiScannerImpl>(); 184 } 185 186 NS_IMETHODIMP nsWifiMonitor::StartWatching(nsIWifiListener* aListener, 187 bool aForcePolling) { 188 LOG(("nsWifiMonitor::StartWatching %p | listener %p | mPollingId %" PRIu64 189 " | aForcePolling %s", 190 this, aListener, static_cast<uint64_t>(mPollingId), 191 aForcePolling ? "true" : "false")); 192 MOZ_ASSERT(NS_IsMainThread()); 193 194 if (!aListener) { 195 return NS_ERROR_NULL_POINTER; 196 } 197 198 if (!mListeners.InsertOrUpdate(aListener, WifiListenerData(aForcePolling), 199 mozilla::fallible)) { 200 return NS_ERROR_OUT_OF_MEMORY; 201 } 202 203 // Run a new scan to update the new listener. If we were polling then 204 // stop that polling and start a new polling interval now. 205 MOZ_ASSERT(mPollingId == 0 || ShouldPoll()); 206 if (aForcePolling) { 207 ++mNumPollingListeners; 208 } 209 if (ShouldPoll()) { 210 mPollingId = NextPollingIndex(); 211 } 212 return DispatchScanToBackgroundThread(mPollingId); 213 } 214 215 NS_IMETHODIMP nsWifiMonitor::StopWatching(nsIWifiListener* aListener) { 216 LOG(("nsWifiMonitor::StopWatching %p | listener %p | mPollingId %" PRIu64, 217 this, aListener, static_cast<uint64_t>(mPollingId))); 218 MOZ_ASSERT(NS_IsMainThread()); 219 220 if (!aListener) { 221 return NS_ERROR_NULL_POINTER; 222 } 223 224 auto maybeData = mListeners.MaybeGet(aListener); 225 if (!maybeData) { 226 return NS_ERROR_INVALID_ARG; 227 } 228 229 if (maybeData->mShouldPoll) { 230 --mNumPollingListeners; 231 } 232 233 mListeners.Remove(aListener); 234 235 if (!ShouldPoll()) { 236 // Stop polling (if we were). 237 LOG(("nsWifiMonitor::StopWatching clearing polling ID")); 238 mPollingId = 0; 239 } 240 241 return NS_OK; 242 } 243 244 nsresult nsWifiMonitor::DispatchScanToBackgroundThread(uint64_t aPollingId, 245 uint32_t aWaitMs) { 246 RefPtr<Runnable> runnable = NewRunnableMethod<uint64_t>( 247 "WifiScannerThread", this, &nsWifiMonitor::Scan, aPollingId); 248 249 if (!mThread) { 250 MOZ_ASSERT(NS_IsMainThread()); 251 252 #ifndef XP_MACOSX 253 nsIThreadManager::ThreadCreationOptions options = {}; 254 #else 255 // If this ASSERT fails, we've increased our default stack size and 256 // may no longer need to special-case the stack size on macOS. 257 static_assert(kMacOSWifiMonitorStackSize > 258 nsIThreadManager::DEFAULT_STACK_SIZE); 259 260 // Mac needs a stack size larger than the default for CoreWLAN. 261 nsIThreadManager::ThreadCreationOptions options = { 262 .stackSize = kMacOSWifiMonitorStackSize}; 263 #endif 264 265 nsresult rv = NS_NewNamedThread("Wifi Monitor", getter_AddRefs(mThread), 266 nullptr, options); 267 NS_ENSURE_SUCCESS(rv, rv); 268 } 269 270 if (aWaitMs) { 271 return mThread->DelayedDispatch(runnable.forget(), aWaitMs); 272 } 273 274 return mThread->Dispatch(runnable.forget()); 275 } 276 277 bool nsWifiMonitor::IsBackgroundThread() { 278 return NS_GetCurrentThread() == mThread; 279 } 280 281 void nsWifiMonitor::Scan(uint64_t aPollingId) { 282 MOZ_ASSERT(IsBackgroundThread()); 283 LOG(("nsWifiMonitor::Scan aPollingId: %" PRIu64 " | mPollingId: %" PRIu64, 284 aPollingId, static_cast<uint64_t>(mPollingId))); 285 286 // If we are using a stale polling ID then stop. If this request to 287 // Scan is not for polling (aPollingId is 0) then always allow it. 288 if (aPollingId && mPollingId != aPollingId) { 289 LOG(("nsWifiMonitor::Scan stopping polling")); 290 return; 291 } 292 293 LOG(("nsWifiMonitor::Scan starting DoScan with id: %" PRIu64, aPollingId)); 294 nsresult rv = DoScan(); 295 LOG(("nsWifiMonitor::Scan DoScan complete | rv = %d", 296 static_cast<uint32_t>(rv))); 297 298 if (NS_FAILED(rv)) { 299 rv = NS_DispatchToMainThread(NewRunnableMethod<nsresult>( 300 "PassErrorToWifiListeners", this, 301 &nsWifiMonitor::PassErrorToWifiListeners, rv)); 302 MOZ_ASSERT(NS_SUCCEEDED(rv)); 303 } 304 305 // If we are polling then we re-issue Scan after a delay. 306 // We re-check the polling IDs since mPollingId may have changed. 307 if (aPollingId && aPollingId == mPollingId) { 308 uint32_t periodMs = StaticPrefs::network_wifi_scanning_period(); 309 if (periodMs) { 310 LOG(("nsWifiMonitor::Scan requesting future scan with id: %" PRIu64 311 " | periodMs: %u", 312 aPollingId, periodMs)); 313 DispatchScanToBackgroundThread(aPollingId, periodMs); 314 } else { 315 // Polling for wifi-scans is disabled. 316 mPollingId = 0; 317 } 318 } 319 320 LOG(("nsWifiMonitor::Scan complete")); 321 } 322 323 nsresult nsWifiMonitor::DoScan() { 324 MOZ_ASSERT(IsBackgroundThread()); 325 326 EnsureWifiScanner(); 327 MOZ_ASSERT(mWifiScanner); 328 329 LOG(("Scanning Wifi for access points")); 330 nsTArray<RefPtr<nsIWifiAccessPoint>> accessPoints; 331 nsresult rv = mWifiScanner->GetAccessPointsFromWLAN(accessPoints); 332 if (NS_FAILED(rv)) { 333 return rv; 334 } 335 336 LOG(("Sorting wifi access points")); 337 accessPoints.Sort([](const RefPtr<nsIWifiAccessPoint>& ia, 338 const RefPtr<nsIWifiAccessPoint>& ib) { 339 const auto& a = static_cast<const nsWifiAccessPoint&>(*ia); 340 const auto& b = static_cast<const nsWifiAccessPoint&>(*ib); 341 return a.Compare(b); 342 }); 343 344 // Sorted compare to see if access point list has changed. 345 LOG(("Checking for new access points")); 346 bool accessPointsChanged = 347 accessPoints.Length() != mLastAccessPoints.Length(); 348 if (!accessPointsChanged) { 349 auto itAp = accessPoints.begin(); 350 auto itLastAp = mLastAccessPoints.begin(); 351 while (itAp != accessPoints.end()) { 352 const auto& a = static_cast<const nsWifiAccessPoint&>(**itAp); 353 const auto& b = static_cast<const nsWifiAccessPoint&>(**itLastAp); 354 if (a != b) { 355 accessPointsChanged = true; 356 break; 357 } 358 ++itAp; 359 ++itLastAp; 360 } 361 } 362 363 mLastAccessPoints = std::move(accessPoints); 364 365 LOG(("Sending Wifi access points to the main thread")); 366 auto* mainThread = GetMainThreadSerialEventTarget(); 367 if (!mainThread) { 368 return NS_ERROR_UNEXPECTED; 369 } 370 371 return NS_DispatchToMainThread( 372 NewRunnableMethod<nsTArray<RefPtr<nsIWifiAccessPoint>>, bool>( 373 "CallWifiListeners", this, &nsWifiMonitor::CallWifiListeners, 374 mLastAccessPoints.Clone(), accessPointsChanged)); 375 } 376 377 template <typename CallbackFn> 378 nsresult nsWifiMonitor::NotifyListeners(CallbackFn&& aCallback) { 379 // Listeners may (un)register other listeners while we iterate, 380 // so we iterate over a copy and re-check membership as we go. 381 // Iteration order is not important. 382 auto listenersCopy(mListeners.Clone()); 383 for (auto iter = listenersCopy.begin(); iter != listenersCopy.end(); ++iter) { 384 auto maybeIter = mListeners.MaybeGet(iter->GetKey()); 385 if (maybeIter) { 386 aCallback(iter->GetKey(), *maybeIter); 387 } 388 } 389 return NS_OK; 390 } 391 392 nsresult nsWifiMonitor::CallWifiListeners( 393 const nsTArray<RefPtr<nsIWifiAccessPoint>>& aAccessPoints, 394 bool aAccessPointsChanged) { 395 MOZ_ASSERT(NS_IsMainThread()); 396 LOG(("Sending wifi access points to the listeners")); 397 return NotifyListeners( 398 [&](nsIWifiListener* aListener, WifiListenerData& aListenerData) { 399 if (!aListenerData.mHasSentData || aAccessPointsChanged) { 400 aListenerData.mHasSentData = true; 401 aListener->OnChange(aAccessPoints); 402 } 403 }); 404 } 405 406 nsresult nsWifiMonitor::PassErrorToWifiListeners(nsresult rv) { 407 MOZ_ASSERT(NS_IsMainThread()); 408 LOG(("About to send error to the wifi listeners")); 409 return NotifyListeners([&](nsIWifiListener* aListener, WifiListenerData&) { 410 aListener->OnError(rv); 411 }); 412 } 413 414 bool nsWifiMonitor::GetHasWifiAdapter() { 415 #ifdef XP_WIN 416 EnsureWifiScanner(); 417 MOZ_ASSERT(mWifiScanner); 418 return static_cast<WifiScannerImpl*>(mWifiScanner.get())->HasWifiAdapter(); 419 #else 420 MOZ_ASSERT_UNREACHABLE( 421 "nsWifiMonitor::HasWifiAdapter is not available on this platform"); 422 return false; 423 #endif 424 }