tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

GeolocationSystemWin.cpp (13814B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include <windows.security.authorization.appcapabilityaccess.h>
      8 #include <windows.system.h>
      9 #include <wrl.h>
     10 
     11 #include "GeolocationSystem.h"
     12 #include "mozilla/Components.h"
     13 #include "mozilla/ScopeExit.h"
     14 #include "mozilla/dom/BrowsingContext.h"
     15 #include "nsIGeolocationUIUtilsWin.h"
     16 #include "nsIWifiListener.h"
     17 #include "nsIWifiMonitor.h"
     18 
     19 namespace mozilla::dom::geolocation {
     20 
     21 using namespace ABI::Windows::Foundation;
     22 using namespace ABI::Windows::Security::Authorization::AppCapabilityAccess;
     23 using namespace ABI::Windows::System;
     24 using namespace Microsoft::WRL;
     25 using Wrappers::HStringReference;
     26 
     27 namespace {
     28 
     29 const auto& kAppCapabilityGuid =
     30    RuntimeClass_Windows_Security_Authorization_AppCapabilityAccess_AppCapability;
     31 const auto& kLauncherGuid = RuntimeClass_Windows_System_Launcher;
     32 const auto& kUriGuid = RuntimeClass_Windows_Foundation_Uri;
     33 const wchar_t kLocationSettingsPage[] = L"ms-settings:privacy-location";
     34 
     35 template <typename TypeToCreate>
     36 ComPtr<TypeToCreate> CreateFromActivationFactory(const wchar_t* aNamespace) {
     37  ComPtr<TypeToCreate> newObject;
     38  GetActivationFactory(HStringReference(aNamespace).Get(), &newObject);
     39  return newObject;
     40 }
     41 
     42 RefPtr<IAppCapability> GetWifiControlAppCapability() {
     43  ComPtr<IAppCapabilityStatics> appCapabilityStatics =
     44      CreateFromActivationFactory<IAppCapabilityStatics>(kAppCapabilityGuid);
     45  NS_ENSURE_TRUE(appCapabilityStatics, nullptr);
     46 
     47  RefPtr<IAppCapability> appCapability;
     48  HRESULT hr = appCapabilityStatics->Create(
     49      HStringReference(L"wifiControl").Get(), getter_AddRefs(appCapability));
     50  NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
     51  NS_ENSURE_TRUE(appCapability, nullptr);
     52  return appCapability;
     53 }
     54 
     55 Maybe<AppCapabilityAccessStatus> GetWifiControlAccess(
     56    IAppCapability* aWifiControlAppCapability) {
     57  NS_ENSURE_TRUE(aWifiControlAppCapability, Nothing());
     58  AppCapabilityAccessStatus status;
     59  HRESULT hr = aWifiControlAppCapability->CheckAccess(&status);
     60  NS_ENSURE_TRUE(SUCCEEDED(hr), Nothing());
     61  return Some(status);
     62 }
     63 
     64 Maybe<AppCapabilityAccessStatus> GetWifiControlAccess() {
     65  RefPtr wifiControlAppCapability = GetWifiControlAppCapability();
     66  // Hold on to the RefPtr through the lifetime of the callee
     67  return GetWifiControlAccess(wifiControlAppCapability.get());
     68 }
     69 
     70 // Takes in wifiAccess instead of calculating here so we can avoid calling
     71 // GetWifiControlAccess() multiple times, which can be slow (bug 1972405).
     72 bool SystemWillPromptForPermissionHint(
     73    Maybe<AppCapabilityAccessStatus> aWifiAccess) {
     74  if (aWifiAccess !=
     75      mozilla::Some(AppCapabilityAccessStatus::
     76                        AppCapabilityAccessStatus_UserPromptRequired)) {
     77    return false;
     78  }
     79 
     80  // If wifi is not available (e.g. because there is no wifi device present)
     81  // then the API may report that Windows will request geolocation permission
     82  // but it can't without the wifi scanner.  Check for that case.
     83  nsCOMPtr<nsIWifiMonitor> wifiMonitor = components::WifiMonitor::Service();
     84  NS_ENSURE_TRUE(wifiMonitor, false);
     85  return wifiMonitor->GetHasWifiAdapter();
     86 }
     87 
     88 // Takes in wifiAccess instead of calculating here so we can avoid calling
     89 // GetWifiControlAccess() multiple times, which can be slow (bug 1972405).
     90 bool LocationIsPermittedHint(Maybe<AppCapabilityAccessStatus> aWifiAccess) {
     91  // This API wasn't available on earlier versions of Windows, so a failure to
     92  // get the result means that we will assume that location access is permitted.
     93  return aWifiAccess.isNothing() ||
     94         *aWifiAccess ==
     95             AppCapabilityAccessStatus::AppCapabilityAccessStatus_Allowed;
     96 }
     97 
     98 class WindowsGeolocationPermissionRequest final
     99    : public SystemGeolocationPermissionRequest,
    100      public SupportsThreadSafeWeakPtr<WindowsGeolocationPermissionRequest> {
    101 public:
    102  MOZ_DECLARE_REFCOUNTED_TYPENAME(WindowsGeolocationPermissionRequest);
    103  // Define SystemGeolocationPermissionRequest's ref-counting by forwarding the
    104  // calls to SupportsThreadSafeWeakPtr<WindowsGeolocationPermissionRequest>
    105  NS_INLINE_DECL_REFCOUNTING_INHERITED(
    106      WindowsGeolocationPermissionRequest,
    107      SupportsThreadSafeWeakPtr<WindowsGeolocationPermissionRequest>);
    108 
    109  WindowsGeolocationPermissionRequest(BrowsingContext* aBrowsingContext,
    110                                      ParentRequestResolver&& aResolver)
    111      : mResolver(std::move(aResolver)), mBrowsingContext(aBrowsingContext) {}
    112 
    113  void Initialize() {
    114    MOZ_ASSERT(!mIsRunning);
    115    auto failedToWatch = MakeScopeExit([&]() {
    116      if (!mIsRunning) {
    117        mAppCapability = nullptr;
    118        mToken = EventRegistrationToken{};
    119        mResolver(GeolocationPermissionStatus::Error);
    120      }
    121    });
    122 
    123    mAppCapability = GetWifiControlAppCapability();
    124    if (!mAppCapability) {
    125      return;
    126    }
    127 
    128    using AccessChangedHandler =
    129        ITypedEventHandler<AppCapability*,
    130                           AppCapabilityAccessChangedEventArgs*>;
    131 
    132    // Note: This creates the callback that listens for location permission
    133    // changes as free threaded, which we need to do to overcome a (Microsoft)
    134    // issue with the callback proxy's exports with (at least) the pre-24H2
    135    // versions of Windows.
    136    ComPtr<AccessChangedHandler> acHandlerRef = Callback<Implements<
    137        RuntimeClassFlags<ClassicCom>, AccessChangedHandler, FtmBase>>(
    138        [weakSelf = ThreadSafeWeakPtr<WindowsGeolocationPermissionRequest>(
    139             this)](IAppCapability*, IAppCapabilityAccessChangedEventArgs*) {
    140          // Because of the free threaded access mentioned above, our
    141          // callback can run on a background thread, so dispatch it to
    142          // main.
    143          if (!NS_IsMainThread()) {
    144            NS_DispatchToMainThread(
    145                NS_NewRunnableFunction(__func__, [weakSelf]() {
    146                  RefPtr<WindowsGeolocationPermissionRequest> self(weakSelf);
    147                  if (self) {
    148                    self->StopIfLocationIsPermitted();
    149                  }
    150                }));
    151            return S_OK;
    152          }
    153 
    154          RefPtr<WindowsGeolocationPermissionRequest> self(weakSelf);
    155          if (self) {
    156            self->StopIfLocationIsPermitted();
    157          }
    158          return S_OK;
    159        });
    160 
    161    if (!acHandlerRef) {
    162      return;
    163    }
    164 
    165    HRESULT hr = mAppCapability->add_AccessChanged(acHandlerRef.Get(), &mToken);
    166    NS_ENSURE_TRUE_VOID(SUCCEEDED(hr));
    167    MOZ_ASSERT(mToken.value);
    168 
    169    mIsRunning = true;
    170    failedToWatch.release();
    171 
    172    // Avoid a race for accessChanged by checking it now and stopping if
    173    // permission is already granted.
    174    StopIfLocationIsPermitted();
    175  }
    176 
    177 protected:
    178  void Stop(Maybe<AppCapabilityAccessStatus> aWifiAccess) {
    179    MOZ_ASSERT(NS_IsMainThread());
    180    if (!mIsRunning) {
    181      return;
    182    }
    183    mIsRunning = false;
    184 
    185    if (LocationIsPermittedHint(aWifiAccess)) {
    186      mResolver(GeolocationPermissionStatus::Granted);
    187    } else {
    188      mResolver(GeolocationPermissionStatus::Canceled);
    189    }
    190 
    191    // Remove the modal with the cancel button.
    192    nsresult rv = DismissPrompt();
    193    NS_ENSURE_SUCCESS_VOID(rv);
    194 
    195    // Stop watching location settings.
    196    MOZ_ASSERT(mAppCapability);
    197    mAppCapability->remove_AccessChanged(mToken);
    198    mAppCapability = nullptr;
    199    mToken = EventRegistrationToken{};
    200  }
    201 
    202 public:
    203  void Stop() override {
    204    MOZ_ASSERT(NS_IsMainThread());
    205    if (!mIsRunning) {
    206      return;
    207    }
    208    Stop(GetWifiControlAccess(mAppCapability));
    209  }
    210 
    211  bool IsStopped() { return !mIsRunning; }
    212 
    213 protected:
    214  // Ref-counting demands that the destructor be non-public but
    215  // SupportsThreadSafeWeakPtr needs to be able to call it, because we use
    216  // NS_INLINE_DECL_REFCOUNTING_INHERITED.
    217  friend SupportsThreadSafeWeakPtr<WindowsGeolocationPermissionRequest>;
    218 
    219  virtual ~WindowsGeolocationPermissionRequest() {
    220    Stop();
    221    MOZ_ASSERT(mToken.value == 0);
    222  }
    223 
    224  void StopIfLocationIsPermitted() {
    225    auto wifiAccess = GetWifiControlAccess(mAppCapability);
    226    if (LocationIsPermittedHint(wifiAccess)) {
    227      Stop(wifiAccess);
    228    }
    229  }
    230 
    231  nsresult DismissPrompt() {
    232    nsresult rv;
    233    nsCOMPtr<nsIGeolocationUIUtilsWin> utils =
    234        do_GetService("@mozilla.org/geolocation/ui-utils-win;1", &rv);
    235    NS_ENSURE_SUCCESS(rv, rv);
    236    return utils->DismissPrompts(mBrowsingContext);
    237  }
    238 
    239  // This IAppCapability object must be held for the duration of the period
    240  // where we listen for location permission changes or else the callback
    241  // will not be called.
    242  RefPtr<IAppCapability> mAppCapability;
    243  ParentRequestResolver mResolver;
    244  RefPtr<BrowsingContext> mBrowsingContext;
    245  EventRegistrationToken mToken;
    246  bool mIsRunning = false;
    247 };
    248 
    249 // Opens Windows geolocation settings and cancels the geolocation request on
    250 // error.
    251 void OpenWindowsLocationSettings(
    252    SystemGeolocationPermissionRequest* aPermissionRequest) {
    253  auto cancelRequest = MakeScopeExit([&]() { aPermissionRequest->Stop(); });
    254 
    255  ComPtr<IUriRuntimeClassFactory> uriFactory =
    256      CreateFromActivationFactory<IUriRuntimeClassFactory>(kUriGuid);
    257  NS_ENSURE_TRUE_VOID(uriFactory);
    258 
    259  RefPtr<IUriRuntimeClass> uri;
    260  HRESULT hr = uriFactory->CreateUri(
    261      HStringReference(kLocationSettingsPage).Get(), getter_AddRefs(uri));
    262  NS_ENSURE_TRUE_VOID(SUCCEEDED(hr));
    263 
    264  ComPtr<ILauncherStatics> launcherStatics =
    265      CreateFromActivationFactory<ILauncherStatics>(kLauncherGuid);
    266  NS_ENSURE_TRUE_VOID(launcherStatics);
    267 
    268  RefPtr<IAsyncOperation<bool>> handler;
    269  hr = launcherStatics->LaunchUriAsync(uri, getter_AddRefs(handler));
    270  NS_ENSURE_TRUE_VOID(SUCCEEDED(hr));
    271 
    272  // The IAsyncOperation is similar to a promise so there is no race here,
    273  // despite us adding this callback after requesting launch instead of before.
    274  handler->put_Completed(
    275      Callback<IAsyncOperationCompletedHandler<bool>>(
    276          [permissionRequest = RefPtr{aPermissionRequest}](
    277              IAsyncOperation<bool>* asyncInfo, AsyncStatus status) {
    278            unsigned char verdict = 0;
    279            asyncInfo->GetResults(&verdict);
    280            if (!verdict) {
    281              permissionRequest->Stop();
    282            }
    283            return S_OK;
    284          })
    285          .Get());
    286 
    287  cancelRequest.release();
    288 }
    289 
    290 class LocationPermissionWifiScanListener final : public nsIWifiListener {
    291 public:
    292  NS_DECL_ISUPPORTS
    293 
    294  explicit LocationPermissionWifiScanListener(
    295      SystemGeolocationPermissionRequest* aRequest)
    296      : mRequest(aRequest) {}
    297 
    298  NS_IMETHOD OnChange(const nsTArray<RefPtr<nsIWifiAccessPoint>>&) override {
    299    // We will remove ourselves from the nsIWifiMonitor, which is our owner.
    300    // Hold a strong reference to ourselves until we complete the callback.
    301    RefPtr<LocationPermissionWifiScanListener> self = this;
    302    return PermissionWasDecided();
    303  }
    304 
    305  NS_IMETHOD OnError(nsresult) override {
    306    // We will remove ourselves from the nsIWifiMonitor, which is our owner.
    307    // Hold a strong reference to ourselves until we complete the callback.
    308    RefPtr<LocationPermissionWifiScanListener> self = this;
    309    return PermissionWasDecided();
    310  }
    311 
    312 private:
    313  virtual ~LocationPermissionWifiScanListener() = default;
    314  RefPtr<SystemGeolocationPermissionRequest> mRequest;
    315 
    316  // Any response to our wifi scan request means that the user has selected
    317  // either to grant or deny permission in the Windows dialog.  Either way, we
    318  // are done asking for permission, so Stop the permission request.
    319  nsresult PermissionWasDecided() {
    320    nsCOMPtr<nsIWifiMonitor> wifiMonitor = components::WifiMonitor::Service();
    321    NS_ENSURE_TRUE(wifiMonitor, NS_ERROR_FAILURE);
    322    wifiMonitor->StopWatching(this);
    323    mRequest->Stop();
    324    return NS_OK;
    325  }
    326 };
    327 
    328 NS_IMPL_ISUPPORTS(LocationPermissionWifiScanListener, nsIWifiListener)
    329 
    330 }  // namespace
    331 
    332 //-----------------------------------------------------------------------------
    333 
    334 SystemGeolocationPermissionBehavior GetGeolocationPermissionBehavior() {
    335  auto wifiAccess = GetWifiControlAccess();
    336  if (SystemWillPromptForPermissionHint(wifiAccess)) {
    337    return SystemGeolocationPermissionBehavior::SystemWillPromptUser;
    338  }
    339  if (!LocationIsPermittedHint(wifiAccess)) {
    340    return SystemGeolocationPermissionBehavior::GeckoWillPromptUser;
    341  }
    342  return SystemGeolocationPermissionBehavior::NoPrompt;
    343 }
    344 
    345 already_AddRefed<SystemGeolocationPermissionRequest>
    346 RequestLocationPermissionFromUser(BrowsingContext* aBrowsingContext,
    347                                  ParentRequestResolver&& aResolver) {
    348  RefPtr<WindowsGeolocationPermissionRequest> permissionRequest =
    349      new WindowsGeolocationPermissionRequest(aBrowsingContext,
    350                                              std::move(aResolver));
    351  permissionRequest->Initialize();
    352  if (permissionRequest->IsStopped()) {
    353    return nullptr;
    354  }
    355  if (SystemWillPromptForPermissionHint(GetWifiControlAccess())) {
    356    // To tell the system to prompt for permission, run one wifi scan (no need
    357    // to poll). We won't use the result -- either the user will grant
    358    // geolocation permission, meaning we will not need wifi scanning, or the
    359    // user will deny permission, in which case no scan can be done.  We just
    360    // want the prompt.
    361    nsCOMPtr<nsIWifiMonitor> wifiMonitor = components::WifiMonitor::Service();
    362    NS_ENSURE_TRUE(wifiMonitor, nullptr);
    363    auto listener =
    364        MakeRefPtr<LocationPermissionWifiScanListener>(permissionRequest);
    365    wifiMonitor->StartWatching(listener, false);
    366  } else {
    367    OpenWindowsLocationSettings(permissionRequest);
    368  }
    369  return permissionRequest.forget();
    370 }
    371 
    372 }  // namespace mozilla::dom::geolocation