diff options
Diffstat (limited to 'dom/system/linux/PortalLocationProvider.cpp')
-rw-r--r-- | dom/system/linux/PortalLocationProvider.cpp | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/dom/system/linux/PortalLocationProvider.cpp b/dom/system/linux/PortalLocationProvider.cpp new file mode 100644 index 0000000000..6ebb1854dc --- /dev/null +++ b/dom/system/linux/PortalLocationProvider.cpp @@ -0,0 +1,351 @@ +/* -*- 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 "MLSFallback.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Logging.h" +#include "mozilla/dom/GeolocationPositionErrorBinding.h" +#include "GeolocationPosition.h" +#include "prtime.h" +#include "mozilla/GUniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/XREAppData.h" + +#include <gio/gio.h> +#include <glib-object.h> + +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<nsIGeolocationUpdate> 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<nsIDOMGeoPositionCoords> 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<nsIGeolocationUpdate> 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<MLSFallback>(0); + mMLSProvider->Startup(new MLSGeolocationUpdate(mCallback)); + } + + nsCOMPtr<nsIGeolocationUpdate> 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<PortalLocationProvider*>(user_data); + RefPtr<GVariant> 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<gchar>(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<double>(); + g_variant_lookup(response_data, "Altitude", "d", &alt); + double vError = 0; + double hError = UnspecifiedNaN<double>(); + g_variant_lookup(response_data, "Accuracy", "d", &hError); + double heading = UnspecifiedNaN<double>(); + g_variant_lookup(response_data, "Heading", "d", &heading); + double speed = UnspecifiedNaN<double>(); + 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<GError> 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->GetDBusAppName(appName); + 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<GVariant> 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<MLSFallback>(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<GDBusMessage> message = dont_AddRef(g_dbus_message_new_method_call( + kDesktopBusName, mPortalSession.get(), kSessionInterfaceName, "Close")); + mPortalSession = nullptr; + if (message) { + GUniquePtr<GError> 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 |