Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#include "nsCOMPtr.h"
#include "nsProxyRelease.h"
#include "nsComponentManagerUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "nsXPCOM.h"
#include "nsXPCOMCID.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsWifiMonitor.h"
#include "nsWifiAccessPoint.h"
#include "nsINetworkLinkService.h"
#include "nsQueryObject.h"
#include "nsNetCID.h"
#include "nsComponentManagerUtils.h"
#include "mozilla/Components.h"
#include "mozilla/DelayedRunnable.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/Services.h"
#if defined(XP_WIN)
# include "WinWifiScanner.h"
#endif
#if defined(XP_MACOSX)
# include "MacWifiScanner.h"
#endif
#if defined(__DragonFly__) || defined(__FreeBSD__)
# include "FreeBsdWifiScanner.h"
#endif
#if defined(XP_SOLARIS)
# include "SolarisWifiScanner.h"
#endif
#if defined(NECKO_WIFI_DBUS)
# include "DbusWifiScanner.h"
#endif
using namespace mozilla;
LazyLogModule gWifiMonitorLog("WifiMonitor");
#define LOG(args) MOZ_LOG(gWifiMonitorLog, mozilla::LogLevel::Debug, args)
NS_IMPL_ISUPPORTS(nsWifiMonitor, nsIObserver, nsIWifiMonitor)
// Main thread only.
static uint64_t sNextPollingIndex = 1;
static uint64_t NextPollingIndex() {
MOZ_ASSERT(NS_IsMainThread());
++sNextPollingIndex;
// Any non-zero value is valid and we don't care about overflow beyond
// that we never want the index to be zero.
if (sNextPollingIndex == 0) {
++sNextPollingIndex;
}
return sNextPollingIndex;
}
// Should we poll wifi or just check it when our network changes?
// We poll when we are on a network where the wifi environment
// could reasonably be expected to change much -- so, on mobile.
static bool ShouldPollForNetworkType(const char16_t* aLinkType) {
auto linkTypeU8 = NS_ConvertUTF16toUTF8(aLinkType);
return linkTypeU8 == NS_NETWORK_LINK_TYPE_WIMAX ||
linkTypeU8 == NS_NETWORK_LINK_TYPE_MOBILE ||
linkTypeU8 == NS_NETWORK_LINK_TYPE_UNKNOWN;
}
// Enum value version.
static bool ShouldPollForNetworkType(uint32_t aLinkType) {
return aLinkType == nsINetworkLinkService::LINK_TYPE_WIMAX ||
aLinkType == nsINetworkLinkService::LINK_TYPE_MOBILE ||
aLinkType == nsINetworkLinkService::LINK_TYPE_UNKNOWN;
}
nsWifiMonitor::nsWifiMonitor(UniquePtr<mozilla::WifiScanner>&& aScanner)
: mWifiScanner(std::move(aScanner)) {
LOG(("Creating nsWifiMonitor"));
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
if (obsSvc) {
obsSvc->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
obsSvc->AddObserver(this, NS_NETWORK_LINK_TYPE_TOPIC, false);
obsSvc->AddObserver(this, "xpcom-shutdown", false);
}
nsresult rv;
nsCOMPtr<nsINetworkLinkService> nls;
nls = do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv) && nls) {
uint32_t linkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
rv = nls->GetLinkType(&linkType);
if (NS_SUCCEEDED(rv)) {
mShouldPollForCurrentNetwork = ShouldPollForNetworkType(linkType);
if (ShouldPoll()) {
mPollingId = NextPollingIndex();
DispatchScanToBackgroundThread(mPollingId);
}
LOG(("nsWifiMonitor network type: %u | shouldPoll: %s", linkType,
mShouldPollForCurrentNetwork ? "true" : "false"));
}
}
}
nsWifiMonitor::~nsWifiMonitor() { LOG(("Destroying nsWifiMonitor")); }
void nsWifiMonitor::Close() {
nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
if (obsSvc) {
obsSvc->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
obsSvc->RemoveObserver(this, NS_NETWORK_LINK_TYPE_TOPIC);
obsSvc->RemoveObserver(this, "xpcom-shutdown");
}
mPollingId = 0;
if (mThread) {
mThread->Shutdown();
}
}
NS_IMETHODIMP
nsWifiMonitor::Observe(nsISupports* subject, const char* topic,
const char16_t* data) {
MOZ_ASSERT(NS_IsMainThread());
if (!strcmp(topic, "xpcom-shutdown")) {
// Make sure any wifi-polling stops.
LOG(("nsWifiMonitor received shutdown"));
Close();
} else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
// Network connectivity has either been gained, lost, or changed (e.g.
// by changing Wifi network). Issue an immediate one-time scan.
// If we were polling, keep polling.
LOG(("nsWifiMonitor %p | mPollingId %" PRIu64
" | received: " NS_NETWORK_LINK_TOPIC " with status %s",
this, static_cast<uint64_t>(mPollingId),
NS_ConvertUTF16toUTF8(data).get()));
DispatchScanToBackgroundThread(0);
} else if (!strcmp(topic, NS_NETWORK_LINK_TYPE_TOPIC)) {
// Network type has changed (e.g. from wifi to mobile). When on some
// network types, we poll wifi. This event does not indicate that a
// new scan would be beneficial right now, so we only issue one if
// we need to begin polling.
// Use IDs to make sure only one task is polling at a time.
LOG(("nsWifiMonitor %p | mPollingId %" PRIu64
" | received: " NS_NETWORK_LINK_TYPE_TOPIC " with status %s",
this, static_cast<uint64_t>(mPollingId),
NS_ConvertUTF16toUTF8(data).get()));
bool wasPolling = ShouldPoll();
MOZ_ASSERT(wasPolling || mPollingId == 0);
mShouldPollForCurrentNetwork = ShouldPollForNetworkType(data);
if (!wasPolling && ShouldPoll()) {
// We weren't polling, so start now.
mPollingId = NextPollingIndex();
DispatchScanToBackgroundThread(mPollingId);
} else if (!ShouldPoll()) {
// Stop polling if we were.
mPollingId = 0;
}
}
return NS_OK;
}
void nsWifiMonitor::EnsureWifiScanner() {
if (mWifiScanner) {
return;
}
LOG(("Constructing WifiScanner"));
mWifiScanner = MakeUnique<mozilla::WifiScannerImpl>();
}
NS_IMETHODIMP nsWifiMonitor::StartWatching(nsIWifiListener* aListener,
bool aForcePolling) {
LOG(("nsWifiMonitor::StartWatching %p | listener %p | mPollingId %" PRIu64
" | aForcePolling %s",
this, aListener, static_cast<uint64_t>(mPollingId),
aForcePolling ? "true" : "false"));
MOZ_ASSERT(NS_IsMainThread());
if (!aListener) {
return NS_ERROR_NULL_POINTER;
}
if (!mListeners.InsertOrUpdate(aListener, WifiListenerData(aForcePolling),
mozilla::fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Run a new scan to update the new listener. If we were polling then
// stop that polling and start a new polling interval now.
MOZ_ASSERT(mPollingId == 0 || ShouldPoll());
if (aForcePolling) {
++mNumPollingListeners;
}
if (ShouldPoll()) {
mPollingId = NextPollingIndex();
}
return DispatchScanToBackgroundThread(mPollingId);
}
NS_IMETHODIMP nsWifiMonitor::StopWatching(nsIWifiListener* aListener) {
LOG(("nsWifiMonitor::StopWatching %p | listener %p | mPollingId %" PRIu64,
this, aListener, static_cast<uint64_t>(mPollingId)));
MOZ_ASSERT(NS_IsMainThread());
if (!aListener) {
return NS_ERROR_NULL_POINTER;
}
auto maybeData = mListeners.MaybeGet(aListener);
if (!maybeData) {
return NS_ERROR_INVALID_ARG;
}
if (maybeData->mShouldPoll) {
--mNumPollingListeners;
}
mListeners.Remove(aListener);
if (!ShouldPoll()) {
// Stop polling (if we were).
LOG(("nsWifiMonitor::StopWatching clearing polling ID"));
mPollingId = 0;
}
return NS_OK;
}
nsresult nsWifiMonitor::DispatchScanToBackgroundThread(uint64_t aPollingId,
uint32_t aWaitMs) {
RefPtr<Runnable> runnable = NewRunnableMethod<uint64_t>(
"WifiScannerThread", this, &nsWifiMonitor::Scan, aPollingId);
if (!mThread) {
MOZ_ASSERT(NS_IsMainThread());
#ifndef XP_MACOSX
nsIThreadManager::ThreadCreationOptions options = {};
#else
// If this ASSERT fails, we've increased our default stack size and
// may no longer need to special-case the stack size on macOS.
static_assert(kMacOSWifiMonitorStackSize >
nsIThreadManager::DEFAULT_STACK_SIZE);
// Mac needs a stack size larger than the default for CoreWLAN.
nsIThreadManager::ThreadCreationOptions options = {
.stackSize = kMacOSWifiMonitorStackSize};
#endif
nsresult rv = NS_NewNamedThread("Wifi Monitor", getter_AddRefs(mThread),
nullptr, options);
NS_ENSURE_SUCCESS(rv, rv);
}
if (aWaitMs) {
return mThread->DelayedDispatch(runnable.forget(), aWaitMs);
}
return mThread->Dispatch(runnable.forget());
}
bool nsWifiMonitor::IsBackgroundThread() {
return NS_GetCurrentThread() == mThread;
}
void nsWifiMonitor::Scan(uint64_t aPollingId) {
MOZ_ASSERT(IsBackgroundThread());
LOG(("nsWifiMonitor::Scan aPollingId: %" PRIu64 " | mPollingId: %" PRIu64,
aPollingId, static_cast<uint64_t>(mPollingId)));
// If we are using a stale polling ID then stop. If this request to
// Scan is not for polling (aPollingId is 0) then always allow it.
if (aPollingId && mPollingId != aPollingId) {
LOG(("nsWifiMonitor::Scan stopping polling"));
return;
}
LOG(("nsWifiMonitor::Scan starting DoScan with id: %" PRIu64, aPollingId));
nsresult rv = DoScan();
LOG(("nsWifiMonitor::Scan DoScan complete | rv = %d",
static_cast<uint32_t>(rv)));
if (NS_FAILED(rv)) {
rv = NS_DispatchToMainThread(NewRunnableMethod<nsresult>(
"PassErrorToWifiListeners", this,
&nsWifiMonitor::PassErrorToWifiListeners, rv));
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
// If we are polling then we re-issue Scan after a delay.
// We re-check the polling IDs since mPollingId may have changed.
if (aPollingId && aPollingId == mPollingId) {
uint32_t periodMs = StaticPrefs::network_wifi_scanning_period();
if (periodMs) {
LOG(("nsWifiMonitor::Scan requesting future scan with id: %" PRIu64
" | periodMs: %u",
aPollingId, periodMs));
DispatchScanToBackgroundThread(aPollingId, periodMs);
} else {
// Polling for wifi-scans is disabled.
mPollingId = 0;
}
}
LOG(("nsWifiMonitor::Scan complete"));
}
nsresult nsWifiMonitor::DoScan() {
MOZ_ASSERT(IsBackgroundThread());
EnsureWifiScanner();
MOZ_ASSERT(mWifiScanner);
LOG(("Scanning Wifi for access points"));
nsTArray<RefPtr<nsIWifiAccessPoint>> accessPoints;
nsresult rv = mWifiScanner->GetAccessPointsFromWLAN(accessPoints);
if (NS_FAILED(rv)) {
return rv;
}
LOG(("Sorting wifi access points"));
accessPoints.Sort([](const RefPtr<nsIWifiAccessPoint>& ia,
const RefPtr<nsIWifiAccessPoint>& ib) {
const auto& a = static_cast<const nsWifiAccessPoint&>(*ia);
const auto& b = static_cast<const nsWifiAccessPoint&>(*ib);
return a.Compare(b);
});
// Sorted compare to see if access point list has changed.
LOG(("Checking for new access points"));
bool accessPointsChanged =
accessPoints.Length() != mLastAccessPoints.Length();
if (!accessPointsChanged) {
auto itAp = accessPoints.begin();
auto itLastAp = mLastAccessPoints.begin();
while (itAp != accessPoints.end()) {
const auto& a = static_cast<const nsWifiAccessPoint&>(**itAp);
const auto& b = static_cast<const nsWifiAccessPoint&>(**itLastAp);
if (a != b) {
accessPointsChanged = true;
break;
}
++itAp;
++itLastAp;
}
}
mLastAccessPoints = std::move(accessPoints);
LOG(("Sending Wifi access points to the main thread"));
auto* mainThread = GetMainThreadSerialEventTarget();
if (!mainThread) {
return NS_ERROR_UNEXPECTED;
}
return NS_DispatchToMainThread(
NewRunnableMethod<nsTArray<RefPtr<nsIWifiAccessPoint>>, bool>(
"CallWifiListeners", this, &nsWifiMonitor::CallWifiListeners,
mLastAccessPoints.Clone(), accessPointsChanged));
}
template <typename CallbackFn>
nsresult nsWifiMonitor::NotifyListeners(CallbackFn&& aCallback) {
// Listeners may (un)register other listeners while we iterate,
// so we iterate over a copy and re-check membership as we go.
// Iteration order is not important.
auto listenersCopy(mListeners.Clone());
for (auto iter = listenersCopy.begin(); iter != listenersCopy.end(); ++iter) {
auto maybeIter = mListeners.MaybeGet(iter->GetKey());
if (maybeIter) {
aCallback(iter->GetKey(), *maybeIter);
}
}
return NS_OK;
}
nsresult nsWifiMonitor::CallWifiListeners(
const nsTArray<RefPtr<nsIWifiAccessPoint>>& aAccessPoints,
bool aAccessPointsChanged) {
MOZ_ASSERT(NS_IsMainThread());
LOG(("Sending wifi access points to the listeners"));
return NotifyListeners(
[&](nsIWifiListener* aListener, WifiListenerData& aListenerData) {
if (!aListenerData.mHasSentData || aAccessPointsChanged) {
aListenerData.mHasSentData = true;
aListener->OnChange(aAccessPoints);
}
});
}
nsresult nsWifiMonitor::PassErrorToWifiListeners(nsresult rv) {
MOZ_ASSERT(NS_IsMainThread());
LOG(("About to send error to the wifi listeners"));
return NotifyListeners([&](nsIWifiListener* aListener, WifiListenerData&) {
aListener->OnError(rv);
});
}
bool nsWifiMonitor::GetHasWifiAdapter() {
#ifdef XP_WIN
EnsureWifiScanner();
MOZ_ASSERT(mWifiScanner);
return static_cast<WifiScannerImpl*>(mWifiScanner.get())->HasWifiAdapter();
#else
MOZ_ASSERT_UNREACHABLE(
"nsWifiMonitor::HasWifiAdapter is not available on this platform");
return false;
#endif
}