tor-browser

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

PortalLocationProvider.cpp (11585B)


      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 "PortalLocationProvider.h"
      8 
      9 #include <gio/gio.h>
     10 #include <glib-object.h>
     11 
     12 #include "GeolocationPosition.h"
     13 #include "MLSFallback.h"
     14 #include "mozilla/FloatingPoint.h"
     15 #include "mozilla/GUniquePtr.h"
     16 #include "mozilla/Logging.h"
     17 #include "mozilla/XREAppData.h"
     18 #include "mozilla/dom/GeolocationPositionErrorBinding.h"
     19 #include "nsAppShell.h"
     20 #include "prtime.h"
     21 
     22 extern const mozilla::XREAppData* gAppData;
     23 
     24 namespace mozilla::dom {
     25 
     26 #ifdef MOZ_LOGGING
     27 static LazyLogModule sPortalLog("Portal");
     28 #  define LOG_PORTAL(...) MOZ_LOG(sPortalLog, LogLevel::Debug, (__VA_ARGS__))
     29 #else
     30 #  define LOG_PORTAL(...)
     31 #endif /* MOZ_LOGGING */
     32 
     33 const char kDesktopBusName[] = "org.freedesktop.portal.Desktop";
     34 const char kSessionInterfaceName[] = "org.freedesktop.portal.Session";
     35 
     36 /**
     37 * |MLSGeolocationUpdate| provides a fallback if Portal is not supported.
     38 */
     39 class PortalLocationProvider::MLSGeolocationUpdate final
     40    : public nsIGeolocationUpdate {
     41 public:
     42  NS_DECL_ISUPPORTS
     43  NS_DECL_NSIGEOLOCATIONUPDATE
     44 
     45  explicit MLSGeolocationUpdate(nsIGeolocationUpdate* aCallback);
     46 
     47 protected:
     48  ~MLSGeolocationUpdate() = default;
     49 
     50 private:
     51  const nsCOMPtr<nsIGeolocationUpdate> mCallback;
     52 };
     53 
     54 PortalLocationProvider::MLSGeolocationUpdate::MLSGeolocationUpdate(
     55    nsIGeolocationUpdate* aCallback)
     56    : mCallback(aCallback) {
     57  MOZ_ASSERT(mCallback);
     58 }
     59 
     60 NS_IMPL_ISUPPORTS(PortalLocationProvider::MLSGeolocationUpdate,
     61                  nsIGeolocationUpdate);
     62 
     63 // nsIGeolocationUpdate
     64 //
     65 
     66 NS_IMETHODIMP
     67 PortalLocationProvider::MLSGeolocationUpdate::Update(
     68    nsIDOMGeoPosition* aPosition) {
     69  nsCOMPtr<nsIDOMGeoPositionCoords> coords;
     70  aPosition->GetCoords(getter_AddRefs(coords));
     71  if (!coords) {
     72    return NS_ERROR_FAILURE;
     73  }
     74  LOG_PORTAL("MLS is updating position\n");
     75  return mCallback->Update(aPosition);
     76 }
     77 
     78 NS_IMETHODIMP
     79 PortalLocationProvider::MLSGeolocationUpdate::NotifyError(uint16_t aError) {
     80  nsCOMPtr<nsIGeolocationUpdate> callback(mCallback);
     81  return callback->NotifyError(aError);
     82 }
     83 
     84 //
     85 // PortalLocationProvider
     86 //
     87 
     88 PortalLocationProvider::PortalLocationProvider() = default;
     89 
     90 PortalLocationProvider::~PortalLocationProvider() {
     91  if (mDBUSLocationProxy || mRefreshTimer || mMLSProvider) {
     92    NS_WARNING(
     93        "PortalLocationProvider: Shutdown() had not been called before "
     94        "destructor.");
     95    Shutdown();
     96  }
     97 }
     98 
     99 void PortalLocationProvider::Update(nsIDOMGeoPosition* aPosition) {
    100  if (!mCallback) {
    101    return;  // not initialized or already shut down
    102  }
    103 
    104  if (mMLSProvider) {
    105    LOG_PORTAL(
    106        "Update from location portal received: Cancelling fallback MLS "
    107        "provider\n");
    108    mMLSProvider->Shutdown(MLSFallback::ShutdownReason::ProviderResponded);
    109    mMLSProvider = nullptr;
    110  }
    111 
    112  LOG_PORTAL("Send updated location to the callback %p", mCallback.get());
    113  mCallback->Update(aPosition);
    114 
    115  aPosition->GetCoords(getter_AddRefs(mLastGeoPositionCoords));
    116  // Schedule sending repetitive updates because we don't get more until
    117  // position is changed from portal. That would lead to timeout on the
    118  // Firefox side.
    119  SetRefreshTimer(5000);
    120 }
    121 
    122 void PortalLocationProvider::NotifyError(int aError) {
    123  LOG_PORTAL("*****NotifyError %d\n", aError);
    124  if (!mCallback) {
    125    return;  // not initialized or already shut down
    126  }
    127 
    128  if (!mMLSProvider) {
    129    /* With Portal failed, we restart MLS. It will be canceled once we
    130     * get another location from Portal. Start it immediately.
    131     */
    132    mMLSProvider = MakeAndAddRef<MLSFallback>(0);
    133    mMLSProvider->Startup(new MLSGeolocationUpdate(mCallback));
    134  }
    135 
    136  nsCOMPtr<nsIGeolocationUpdate> callback(mCallback);
    137  callback->NotifyError(aError);
    138 }
    139 
    140 NS_IMPL_ISUPPORTS(PortalLocationProvider, nsIGeolocationProvider)
    141 
    142 static void location_updated_signal_cb(GDBusProxy* proxy, gchar* sender_name,
    143                                       gchar* signal_name, GVariant* parameters,
    144                                       gpointer user_data) {
    145  LOG_PORTAL("Signal: %s received from: %s\n", sender_name, signal_name);
    146 
    147  if (g_strcmp0(signal_name, "LocationUpdated")) {
    148    LOG_PORTAL("Unexpected signal %s received", signal_name);
    149    return;
    150  }
    151 
    152  auto* locationProvider = static_cast<PortalLocationProvider*>(user_data);
    153  RefPtr<GVariant> response_data;
    154  gchar* session_handle;
    155  g_variant_get(parameters, "(o@a{sv})", &session_handle,
    156                response_data.StartAssignment());
    157  if (!response_data) {
    158    LOG_PORTAL("Missing response data from portal\n");
    159    locationProvider->NotifyError(
    160        GeolocationPositionError_Binding::POSITION_UNAVAILABLE);
    161    return;
    162  }
    163  LOG_PORTAL("Session handle: %s Response data: %s\n", session_handle,
    164             GUniquePtr<gchar>(g_variant_print(response_data, TRUE)).get());
    165  g_free(session_handle);
    166 
    167  double lat = 0;
    168  double lon = 0;
    169  if (!g_variant_lookup(response_data, "Latitude", "d", &lat) ||
    170      !g_variant_lookup(response_data, "Longitude", "d", &lon)) {
    171    locationProvider->NotifyError(
    172        GeolocationPositionError_Binding::POSITION_UNAVAILABLE);
    173    return;
    174  }
    175 
    176  double alt = UnspecifiedNaN<double>();
    177  g_variant_lookup(response_data, "Altitude", "d", &alt);
    178  double vError = 0;
    179  double hError = UnspecifiedNaN<double>();
    180  g_variant_lookup(response_data, "Accuracy", "d", &hError);
    181  double heading = UnspecifiedNaN<double>();
    182  g_variant_lookup(response_data, "Heading", "d", &heading);
    183  double speed = UnspecifiedNaN<double>();
    184  g_variant_lookup(response_data, "Speed", "d", &speed);
    185 
    186  locationProvider->Update(new nsGeoPosition(lat, lon, alt, hError, vError,
    187                                             heading, speed,
    188                                             PR_Now() / PR_USEC_PER_MSEC));
    189 }
    190 
    191 NS_IMETHODIMP
    192 PortalLocationProvider::Startup() {
    193  LOG_PORTAL("Starting location portal");
    194  if (mDBUSLocationProxy) {
    195    LOG_PORTAL("Proxy already started.\n");
    196    return NS_OK;
    197  }
    198 
    199  // Create dbus proxy for the Location portal
    200  GUniquePtr<GError> error;
    201  nsAppShell::DBusConnectionCheck();
    202  mDBUSLocationProxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync(
    203      G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE,
    204      nullptr, /* GDBusInterfaceInfo */
    205      kDesktopBusName, "/org/freedesktop/portal/desktop",
    206      "org.freedesktop.portal.Location", nullptr, /* GCancellable */
    207      getter_Transfers(error)));
    208  if (!mDBUSLocationProxy) {
    209    g_printerr("Error creating location dbus proxy: %s\n", error->message);
    210    return NS_OK;  // fallback to MLS
    211  }
    212 
    213  // Listen to signals which will be send to us with the location data
    214  mDBUSSignalHandler =
    215      g_signal_connect(mDBUSLocationProxy, "g-signal",
    216                       G_CALLBACK(location_updated_signal_cb), this);
    217 
    218  // Call CreateSession of the location portal
    219  GVariantBuilder builder;
    220 
    221  nsAutoCString appName;
    222  gAppData->GetDBusAppName(appName);
    223  g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
    224  g_variant_builder_add(&builder, "{sv}", "session_handle_token",
    225                        g_variant_new_string(appName.get()));
    226 
    227  RefPtr<GVariant> result = dont_AddRef(g_dbus_proxy_call_sync(
    228      mDBUSLocationProxy, "CreateSession", g_variant_new("(a{sv})", &builder),
    229      G_DBUS_CALL_FLAGS_NONE, -1, nullptr, getter_Transfers(error)));
    230 
    231  g_variant_builder_clear(&builder);
    232 
    233  if (!result) {
    234    g_printerr("Error calling CreateSession method: %s\n", error->message);
    235    return NS_OK;  // fallback to MLS
    236  }
    237 
    238  // Start to listen to the location changes
    239  g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
    240 
    241  // TODO Use wayland:handle as described in
    242  // https://flatpak.github.io/xdg-desktop-portal/#parent_window
    243  const gchar* parent_window = "";
    244  gchar* portalSession;
    245  g_variant_get_child(result, 0, "o", &portalSession);
    246  mPortalSession.reset(portalSession);
    247 
    248  result = g_dbus_proxy_call_sync(
    249      mDBUSLocationProxy, "Start",
    250      g_variant_new("(osa{sv})", mPortalSession.get(), parent_window, &builder),
    251      G_DBUS_CALL_FLAGS_NONE, -1, nullptr, getter_Transfers(error));
    252 
    253  g_variant_builder_clear(&builder);
    254 
    255  if (!result) {
    256    g_printerr("Error calling Start method: %s\n", error->message);
    257    return NS_OK;  // fallback to MLS
    258  }
    259  return NS_OK;
    260 }
    261 
    262 NS_IMETHODIMP
    263 PortalLocationProvider::Watch(nsIGeolocationUpdate* aCallback) {
    264  mCallback = aCallback;
    265 
    266  if (mLastGeoPositionCoords) {
    267    // We cannot immediately call the Update there becase the window is not
    268    // yet ready for that.
    269    LOG_PORTAL(
    270        "Update location in 1ms because we have the valid coords cached.");
    271    SetRefreshTimer(1);
    272    return NS_OK;
    273  }
    274 
    275  /* The MLS fallback will kick in after 12 seconds if portal
    276   * doesn't provide location information within time. Once we
    277   * see the first message from portal, the fallback will be
    278   * disabled in |Update|.
    279   */
    280  mMLSProvider = MakeAndAddRef<MLSFallback>(12000);
    281  mMLSProvider->Startup(new MLSGeolocationUpdate(aCallback));
    282 
    283  return NS_OK;
    284 }
    285 
    286 NS_IMETHODIMP PortalLocationProvider::GetName(nsACString& aName) {
    287  aName.AssignLiteral("PortalLocationProvider");
    288  return NS_OK;
    289 }
    290 
    291 void PortalLocationProvider::SetRefreshTimer(int aDelay) {
    292  LOG_PORTAL("SetRefreshTimer for %p to %d ms\n", this, aDelay);
    293  if (!mRefreshTimer) {
    294    NS_NewTimerWithCallback(getter_AddRefs(mRefreshTimer), this, aDelay,
    295                            nsITimer::TYPE_ONE_SHOT);
    296  } else {
    297    mRefreshTimer->Cancel();
    298    mRefreshTimer->InitWithCallback(this, aDelay, nsITimer::TYPE_ONE_SHOT);
    299  }
    300 }
    301 
    302 NS_IMETHODIMP
    303 PortalLocationProvider::Notify(nsITimer* timer) {
    304  // We need to reschedule the timer because we won't get any update
    305  // from portal until the location is changed. That would cause
    306  // watchPosition to fail with TIMEOUT error.
    307  SetRefreshTimer(5000);
    308  if (mLastGeoPositionCoords) {
    309    LOG_PORTAL("Update location callback with latest coords.");
    310    mCallback->Update(
    311        new nsGeoPosition(mLastGeoPositionCoords, PR_Now() / PR_USEC_PER_MSEC));
    312  }
    313  return NS_OK;
    314 }
    315 
    316 NS_IMETHODIMP
    317 PortalLocationProvider::Shutdown() {
    318  LOG_PORTAL("Shutdown location provider");
    319  if (mRefreshTimer) {
    320    mRefreshTimer->Cancel();
    321    mRefreshTimer = nullptr;
    322  }
    323  mLastGeoPositionCoords = nullptr;
    324  if (mDBUSLocationProxy) {
    325    nsAppShell::DBusConnectionCheck();
    326    g_signal_handler_disconnect(mDBUSLocationProxy, mDBUSSignalHandler);
    327    LOG_PORTAL("calling Close method to the session interface...\n");
    328    RefPtr<GDBusMessage> message = dont_AddRef(g_dbus_message_new_method_call(
    329        kDesktopBusName, mPortalSession.get(), kSessionInterfaceName, "Close"));
    330    mPortalSession = nullptr;
    331    if (message) {
    332      GUniquePtr<GError> error;
    333      GDBusConnection* connection =
    334          g_dbus_proxy_get_connection(mDBUSLocationProxy);
    335      g_dbus_connection_send_message(
    336          connection, message, G_DBUS_SEND_MESSAGE_FLAGS_NONE,
    337          /*out_serial=*/nullptr, getter_Transfers(error));
    338      if (error) {
    339        g_printerr("Failed to close the session: %s\n", error->message);
    340      }
    341    }
    342    mDBUSLocationProxy = nullptr;
    343  }
    344  if (mMLSProvider) {
    345    mMLSProvider->Shutdown(MLSFallback::ShutdownReason::ProviderShutdown);
    346    mMLSProvider = nullptr;
    347  }
    348  return NS_OK;
    349 }
    350 
    351 NS_IMETHODIMP
    352 PortalLocationProvider::SetHighAccuracy(bool aHigh) { return NS_OK; }
    353 
    354 }  // namespace mozilla::dom