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
This commit is contained in:
Jan Horak 2022-04-19 11:42:38 +00:00
parent b3f898a618
commit 705f8983d6
9 changed files with 445 additions and 0 deletions

View File

@ -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

View File

@ -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"]

View File

@ -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 <errno.h>
#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 <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->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<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

View File

@ -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 <gio/gio.h>
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<GDBusProxy> mDBUSLocationProxy;
gulong mDBUSSignalHandler = 0;
GUniquePtr<gchar> mPortalSession;
nsCOMPtr<nsIGeolocationUpdate> mCallback;
RefPtr<MLSFallback> mMLSProvider;
nsCOMPtr<nsIDOMGeoPositionCoords> mLastGeoPositionCoords;
nsCOMPtr<nsITimer> mRefreshTimer;
};
} // namespace mozilla::dom
#endif /* GpsLocationProvider_h */

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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;
}();

View File

@ -44,6 +44,7 @@ enum class PortalKind {
FilePicker,
MimeHandler,
Settings,
Location,
};
bool ShouldUsePortal(PortalKind);