mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
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:
parent
b3f898a618
commit
705f8983d6
@ -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
|
||||
|
@ -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"]
|
||||
|
356
dom/system/linux/PortalLocationProvider.cpp
Normal file
356
dom/system/linux/PortalLocationProvider.cpp
Normal 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
|
54
dom/system/linux/PortalLocationProvider.h
Normal file
54
dom/system/linux/PortalLocationProvider.h
Normal 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 */
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}();
|
||||
|
@ -44,6 +44,7 @@ enum class PortalKind {
|
||||
FilePicker,
|
||||
MimeHandler,
|
||||
Settings,
|
||||
Location,
|
||||
};
|
||||
bool ShouldUsePortal(PortalKind);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user