From 705f8983d6edd6533aeb8d3fdf0c0669a239b511 Mon Sep 17 00:00:00 2001 From: Jan Horak Date: Tue, 19 Apr 2022 11:42:38 +0000 Subject: [PATCH] Bug 1759840 Add support for location portal; r=emilio The Firefox in flatpak has no access to the wireless networks to determine accurate geolocation. We have to use the location portal instead which provides the current location based on the nearby wireless accesspoints or other methods. Differential Revision: https://phabricator.services.mozilla.com/D142329 --- dom/geolocation/Geolocation.cpp | 10 + dom/geolocation/moz.build | 4 + dom/system/linux/PortalLocationProvider.cpp | 356 ++++++++++++++++++++ dom/system/linux/PortalLocationProvider.h | 54 +++ dom/system/linux/moz.build | 7 + modules/libpref/init/StaticPrefList.yaml | 10 + widget/gtk/GRefPtr.h | 1 + widget/gtk/WidgetUtilsGtk.cpp | 2 + widget/gtk/WidgetUtilsGtk.h | 1 + 9 files changed, 445 insertions(+) create mode 100644 dom/system/linux/PortalLocationProvider.cpp create mode 100644 dom/system/linux/PortalLocationProvider.h diff --git a/dom/geolocation/Geolocation.cpp b/dom/geolocation/Geolocation.cpp index 7858c69827b3..5f91e0ba2507 100644 --- a/dom/geolocation/Geolocation.cpp +++ b/dom/geolocation/Geolocation.cpp @@ -43,6 +43,11 @@ class nsIPrincipal; # include "GpsdLocationProvider.h" #endif +#ifdef MOZ_ENABLE_DBUS +# include "mozilla/WidgetUtilsGtk.h" +# include "PortalLocationProvider.h" +#endif + #ifdef MOZ_WIDGET_COCOA # include "CoreLocationLocationProvider.h" #endif @@ -494,6 +499,11 @@ nsresult nsGeolocationService::Init() { mProvider = new GpsdLocationProvider(); } # endif +# ifdef MOZ_ENABLE_DBUS + if (!mProvider && widget::ShouldUsePortal(widget::PortalKind::Location)) { + mProvider = new PortalLocationProvider(); + } +# endif #endif #ifdef MOZ_WIDGET_COCOA diff --git a/dom/geolocation/moz.build b/dom/geolocation/moz.build index 5c7f7471818c..8482c8aa1fc1 100644 --- a/dom/geolocation/moz.build +++ b/dom/geolocation/moz.build @@ -55,3 +55,7 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": "/dom/system/linux", ] DEFINES["MOZ_GPSD"] = True + if CONFIG["MOZ_ENABLE_DBUS"]: + LOCAL_INCLUDES += ["/dom/system/linux"] + CXXFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"] + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] diff --git a/dom/system/linux/PortalLocationProvider.cpp b/dom/system/linux/PortalLocationProvider.cpp new file mode 100644 index 000000000000..bdae871a52f4 --- /dev/null +++ b/dom/system/linux/PortalLocationProvider.cpp @@ -0,0 +1,356 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PortalLocationProvider.h" +#include +#include "MLSFallback.h" +#include "mozilla/Atomics.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/LazyIdleThread.h" +#include "mozilla/Logging.h" +#include "mozilla/dom/GeolocationPositionErrorBinding.h" +#include "GeolocationPosition.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" +#include "prtime.h" +#include "mozilla/GUniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/XREAppData.h" + +#include +#include + +extern const mozilla::XREAppData* gAppData; + +namespace mozilla::dom { + +#ifdef MOZ_LOGGING +static LazyLogModule sPortalLog("Portal"); +# define LOG_PORTAL(...) MOZ_LOG(sPortalLog, LogLevel::Debug, (__VA_ARGS__)) +#else +# define LOG_PORTAL(...) +#endif /* MOZ_LOGGING */ + +const char kDesktopBusName[] = "org.freedesktop.portal.Desktop"; +const char kSessionInterfaceName[] = "org.freedesktop.portal.Session"; + +/** + * |MLSGeolocationUpdate| provides a fallback if Portal is not supported. + */ +class PortalLocationProvider::MLSGeolocationUpdate final + : public nsIGeolocationUpdate { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIGEOLOCATIONUPDATE + + explicit MLSGeolocationUpdate(nsIGeolocationUpdate* aCallback); + + protected: + ~MLSGeolocationUpdate() = default; + + private: + const nsCOMPtr mCallback; +}; + +PortalLocationProvider::MLSGeolocationUpdate::MLSGeolocationUpdate( + nsIGeolocationUpdate* aCallback) + : mCallback(aCallback) { + MOZ_ASSERT(mCallback); +} + +NS_IMPL_ISUPPORTS(PortalLocationProvider::MLSGeolocationUpdate, + nsIGeolocationUpdate); + +// nsIGeolocationUpdate +// + +NS_IMETHODIMP +PortalLocationProvider::MLSGeolocationUpdate::Update( + nsIDOMGeoPosition* aPosition) { + nsCOMPtr coords; + aPosition->GetCoords(getter_AddRefs(coords)); + if (!coords) { + return NS_ERROR_FAILURE; + } + LOG_PORTAL("MLS is updating position\n"); + return mCallback->Update(aPosition); +} + +NS_IMETHODIMP +PortalLocationProvider::MLSGeolocationUpdate::NotifyError(uint16_t aError) { + nsCOMPtr callback(mCallback); + return callback->NotifyError(aError); +} + +// +// PortalLocationProvider +// + +PortalLocationProvider::PortalLocationProvider() = default; + +PortalLocationProvider::~PortalLocationProvider() { + if (mDBUSLocationProxy || mRefreshTimer || mMLSProvider) { + NS_WARNING( + "PortalLocationProvider: Shutdown() had not been called before " + "destructor."); + Shutdown(); + } +} + +void PortalLocationProvider::Update(nsIDOMGeoPosition* aPosition) { + if (!mCallback) { + return; // not initialized or already shut down + } + + if (mMLSProvider) { + LOG_PORTAL( + "Update from location portal received: Cancelling fallback MLS " + "provider\n"); + mMLSProvider->Shutdown(); + mMLSProvider = nullptr; + } + + LOG_PORTAL("Send updated location to the callback %p", mCallback.get()); + mCallback->Update(aPosition); + + aPosition->GetCoords(getter_AddRefs(mLastGeoPositionCoords)); + // Schedule sending repetitive updates because we don't get more until + // position is changed from portal. That would lead to timeout on the + // Firefox side. + SetRefreshTimer(5000); +} + +void PortalLocationProvider::NotifyError(int aError) { + LOG_PORTAL("*****NotifyError %d\n", aError); + if (!mCallback) { + return; // not initialized or already shut down + } + + if (!mMLSProvider) { + /* With Portal failed, we restart MLS. It will be canceled once we + * get another location from Portal. Start it immediately. + */ + mMLSProvider = MakeAndAddRef(0); + mMLSProvider->Startup(new MLSGeolocationUpdate(mCallback)); + } + + nsCOMPtr callback(mCallback); + callback->NotifyError(aError); +} + +NS_IMPL_ISUPPORTS(PortalLocationProvider, nsIGeolocationProvider) + +static void location_updated_signal_cb(GDBusProxy* proxy, gchar* sender_name, + gchar* signal_name, GVariant* parameters, + gpointer user_data) { + LOG_PORTAL("Signal: %s received from: %s\n", sender_name, signal_name); + + if (g_strcmp0(signal_name, "LocationUpdated")) { + LOG_PORTAL("Unexpected signal %s received", signal_name); + return; + } + + auto* locationProvider = static_cast(user_data); + RefPtr response_data; + gchar* session_handle; + g_variant_get(parameters, "(o@a{sv})", &session_handle, + response_data.StartAssignment()); + if (!response_data) { + LOG_PORTAL("Missing response data from portal\n"); + locationProvider->NotifyError( + GeolocationPositionError_Binding::POSITION_UNAVAILABLE); + return; + } + LOG_PORTAL("Session handle: %s Response data: %s\n", session_handle, + GUniquePtr(g_variant_print(response_data, TRUE)).get()); + g_free(session_handle); + + double lat = 0; + double lon = 0; + if (!g_variant_lookup(response_data, "Latitude", "d", &lat) || + !g_variant_lookup(response_data, "Longitude", "d", &lon)) { + locationProvider->NotifyError( + GeolocationPositionError_Binding::POSITION_UNAVAILABLE); + return; + } + + double alt = UnspecifiedNaN(); + g_variant_lookup(response_data, "Altitude", "d", &alt); + double vError = 0; + double hError = UnspecifiedNaN(); + g_variant_lookup(response_data, "Accuracy", "d", &hError); + double heading = UnspecifiedNaN(); + g_variant_lookup(response_data, "Heading", "d", &heading); + double speed = UnspecifiedNaN(); + g_variant_lookup(response_data, "Speed", "d", &speed); + + locationProvider->Update(new nsGeoPosition(lat, lon, alt, hError, vError, + heading, speed, + PR_Now() / PR_USEC_PER_MSEC)); +} + +NS_IMETHODIMP +PortalLocationProvider::Startup() { + LOG_PORTAL("Starting location portal"); + if (mDBUSLocationProxy) { + LOG_PORTAL("Proxy already started.\n"); + return NS_OK; + } + + // Create dbus proxy for the Location portal + GUniquePtr error; + mDBUSLocationProxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync( + G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, + nullptr, /* GDBusInterfaceInfo */ + kDesktopBusName, "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Location", nullptr, /* GCancellable */ + getter_Transfers(error))); + if (!mDBUSLocationProxy) { + g_printerr("Error creating location dbus proxy: %s\n", error->message); + return NS_OK; // fallback to MLS + } + + // Listen to signals which will be send to us with the location data + mDBUSSignalHandler = + g_signal_connect(mDBUSLocationProxy, "g-signal", + G_CALLBACK(location_updated_signal_cb), this); + + // Call CreateSession of the location portal + GVariantBuilder builder; + + nsAutoCString appName(gAppData->remotingName); + appName.ReplaceChar("+/=-", '_'); + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&builder, "{sv}", "session_handle_token", + g_variant_new_string(appName.get())); + + RefPtr result = dont_AddRef(g_dbus_proxy_call_sync( + mDBUSLocationProxy, "CreateSession", g_variant_new("(a{sv})", &builder), + G_DBUS_CALL_FLAGS_NONE, -1, nullptr, getter_Transfers(error))); + + g_variant_builder_clear(&builder); + + if (!result) { + g_printerr("Error calling CreateSession method: %s\n", error->message); + return NS_OK; // fallback to MLS + } + + // Start to listen to the location changes + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + // TODO Use wayland:handle as described in + // https://flatpak.github.io/xdg-desktop-portal/#parent_window + const gchar* parent_window = ""; + gchar* portalSession; + g_variant_get_child(result, 0, "o", &portalSession); + mPortalSession.reset(portalSession); + + result = g_dbus_proxy_call_sync( + mDBUSLocationProxy, "Start", + g_variant_new("(osa{sv})", mPortalSession.get(), parent_window, &builder), + G_DBUS_CALL_FLAGS_NONE, -1, nullptr, getter_Transfers(error)); + + g_variant_builder_clear(&builder); + + if (!result) { + g_printerr("Error calling Start method: %s\n", error->message); + return NS_OK; // fallback to MLS + } + return NS_OK; +} + +NS_IMETHODIMP +PortalLocationProvider::Watch(nsIGeolocationUpdate* aCallback) { + mCallback = aCallback; + + if (mLastGeoPositionCoords) { + // We cannot immediately call the Update there becase the window is not + // yet ready for that. + LOG_PORTAL( + "Update location in 1ms because we have the valid coords cached."); + SetRefreshTimer(1); + return NS_OK; + } + + /* The MLS fallback will kick in after 12 seconds if portal + * doesn't provide location information within time. Once we + * see the first message from portal, the fallback will be + * disabled in |Update|. + */ + mMLSProvider = MakeAndAddRef(12000); + mMLSProvider->Startup(new MLSGeolocationUpdate(aCallback)); + + return NS_OK; +} + +NS_IMETHODIMP PortalLocationProvider::GetName(nsACString& aName) { + aName.AssignLiteral("PortalLocationProvider"); + return NS_OK; +} + +void PortalLocationProvider::SetRefreshTimer(int aDelay) { + LOG_PORTAL("SetRefreshTimer for %p to %d ms\n", this, aDelay); + if (!mRefreshTimer) { + NS_NewTimerWithCallback(getter_AddRefs(mRefreshTimer), this, aDelay, + nsITimer::TYPE_ONE_SHOT); + } else { + mRefreshTimer->Cancel(); + mRefreshTimer->InitWithCallback(this, aDelay, nsITimer::TYPE_ONE_SHOT); + } +} + +NS_IMETHODIMP +PortalLocationProvider::Notify(nsITimer* timer) { + // We need to reschedule the timer because we won't get any update + // from portal until the location is changed. That would cause + // watchPosition to fail with TIMEOUT error. + SetRefreshTimer(5000); + if (mLastGeoPositionCoords) { + LOG_PORTAL("Update location callback with latest coords."); + mCallback->Update( + new nsGeoPosition(mLastGeoPositionCoords, PR_Now() / PR_USEC_PER_MSEC)); + } + return NS_OK; +} + +NS_IMETHODIMP +PortalLocationProvider::Shutdown() { + LOG_PORTAL("Shutdown location provider"); + if (mRefreshTimer) { + mRefreshTimer->Cancel(); + mRefreshTimer = nullptr; + } + mLastGeoPositionCoords = nullptr; + if (mDBUSLocationProxy) { + g_signal_handler_disconnect(mDBUSLocationProxy, mDBUSSignalHandler); + LOG_PORTAL("calling Close method to the session interface...\n"); + RefPtr message = dont_AddRef(g_dbus_message_new_method_call( + kDesktopBusName, mPortalSession.get(), kSessionInterfaceName, "Close")); + mPortalSession = nullptr; + if (message) { + GUniquePtr error; + GDBusConnection* connection = + g_dbus_proxy_get_connection(mDBUSLocationProxy); + g_dbus_connection_send_message( + connection, message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, + /*out_serial=*/nullptr, getter_Transfers(error)); + if (error) { + g_printerr("Failed to close the session: %s\n", error->message); + } + } + mDBUSLocationProxy = nullptr; + } + if (mMLSProvider) { + mMLSProvider->Shutdown(); + mMLSProvider = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +PortalLocationProvider::SetHighAccuracy(bool aHigh) { return NS_OK; } + +} // namespace mozilla::dom diff --git a/dom/system/linux/PortalLocationProvider.h b/dom/system/linux/PortalLocationProvider.h new file mode 100644 index 000000000000..e7ead0ab5c79 --- /dev/null +++ b/dom/system/linux/PortalLocationProvider.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef PortalLocationProvider_h +#define PortalLocationProvider_h + +#include "nsCOMPtr.h" +#include "mozilla/GRefPtr.h" +#include "mozilla/GUniquePtr.h" +#include "Geolocation.h" +#include "nsIGeolocationProvider.h" +#include + +class MLSFallback; + +namespace mozilla::dom { + +class PortalLocationProvider final : public nsIGeolocationProvider, + public nsITimerCallback, + public nsINamed { + class MLSGeolocationUpdate; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIGEOLOCATIONPROVIDER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + PortalLocationProvider(); + + void Update(nsIDOMGeoPosition* aPosition); + MOZ_CAN_RUN_SCRIPT_BOUNDARY + void NotifyError(int aError); + + private: + ~PortalLocationProvider(); + void SetRefreshTimer(int aDelay); + + RefPtr mDBUSLocationProxy; + gulong mDBUSSignalHandler = 0; + + GUniquePtr mPortalSession; + nsCOMPtr mCallback; + RefPtr mMLSProvider; + nsCOMPtr mLastGeoPositionCoords; + nsCOMPtr mRefreshTimer; +}; + +} // namespace mozilla::dom + +#endif /* GpsLocationProvider_h */ diff --git a/dom/system/linux/moz.build b/dom/system/linux/moz.build index ab9d076deb16..840066a51cd4 100644 --- a/dom/system/linux/moz.build +++ b/dom/system/linux/moz.build @@ -13,4 +13,11 @@ if CONFIG["MOZ_GPSD"]: LOCAL_INCLUDES += ["/dom/geolocation"] + +if CONFIG["MOZ_ENABLE_DBUS"]: + SOURCES += ["PortalLocationProvider.cpp"] + LOCAL_INCLUDES += ["/dom/geolocation"] + CXXFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"] + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] + FINAL_LIBRARY = "xul" diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 2264cae0db10..bc4701e9e02e 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -13200,6 +13200,16 @@ type: int32_t value: 2 mirror: always + +# Whether to use XDG portal for geolocation. +# https://flatpak.github.io/xdg-desktop-portal/#gdbus-org.freedesktop.portal.Location +# - 0: never +# - 1: always +# - 2: auto +- name: widget.use-xdg-desktop-portal.location + type: int32_t + value: 2 + mirror: always #endif #ifdef XP_WIN diff --git a/widget/gtk/GRefPtr.h b/widget/gtk/GRefPtr.h index 1ad6b7005401..6c118d68597e 100644 --- a/widget/gtk/GRefPtr.h +++ b/widget/gtk/GRefPtr.h @@ -39,6 +39,7 @@ GOBJECT_TRAITS(GDBusProxy) GOBJECT_TRAITS(GAppInfo) GOBJECT_TRAITS(GAppLaunchContext) GOBJECT_TRAITS(GdkDragContext) +GOBJECT_TRAITS(GDBusMessage) GOBJECT_TRAITS(GdkPixbuf) #ifdef MOZ_ENABLE_DBUS diff --git a/widget/gtk/WidgetUtilsGtk.cpp b/widget/gtk/WidgetUtilsGtk.cpp index 33faa61f1b78..9b75a341b958 100644 --- a/widget/gtk/WidgetUtilsGtk.cpp +++ b/widget/gtk/WidgetUtilsGtk.cpp @@ -136,6 +136,8 @@ bool ShouldUsePortal(PortalKind aPortalKind) { case PortalKind::Settings: autoBehavior = true; return StaticPrefs::widget_use_xdg_desktop_portal_settings(); + case PortalKind::Location: + return StaticPrefs::widget_use_xdg_desktop_portal_location(); } return 2; }(); diff --git a/widget/gtk/WidgetUtilsGtk.h b/widget/gtk/WidgetUtilsGtk.h index cb163d33594c..960e206f23c5 100644 --- a/widget/gtk/WidgetUtilsGtk.h +++ b/widget/gtk/WidgetUtilsGtk.h @@ -44,6 +44,7 @@ enum class PortalKind { FilePicker, MimeHandler, Settings, + Location, }; bool ShouldUsePortal(PortalKind);