summaryrefslogtreecommitdiffstats
path: root/dom/system/linux
diff options
context:
space:
mode:
Diffstat (limited to 'dom/system/linux')
-rw-r--r--dom/system/linux/GeoclueLocationProvider.cpp1060
-rw-r--r--dom/system/linux/GeoclueLocationProvider.h32
-rw-r--r--dom/system/linux/GpsdLocationProvider.cpp446
-rw-r--r--dom/system/linux/GpsdLocationProvider.h51
-rw-r--r--dom/system/linux/PortalLocationProvider.cpp351
-rw-r--r--dom/system/linux/PortalLocationProvider.h54
-rw-r--r--dom/system/linux/moz.build23
7 files changed, 2017 insertions, 0 deletions
diff --git a/dom/system/linux/GeoclueLocationProvider.cpp b/dom/system/linux/GeoclueLocationProvider.cpp
new file mode 100644
index 0000000000..c9f5ef5dac
--- /dev/null
+++ b/dom/system/linux/GeoclueLocationProvider.cpp
@@ -0,0 +1,1060 @@
+/*
+ * 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/.
+ *
+ * Author: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
+ */
+
+#include "GeoclueLocationProvider.h"
+
+#include <gio/gio.h>
+#include <glib.h>
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/GRefPtr.h"
+#include "mozilla/GUniquePtr.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_geo.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/XREAppData.h"
+#include "mozilla/dom/GeolocationPosition.h"
+#include "mozilla/dom/GeolocationPositionErrorBinding.h"
+#include "MLSFallback.h"
+#include "nsAppRunner.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMGeoPosition.h"
+#include "nsINamed.h"
+#include "nsITimer.h"
+#include "nsStringFwd.h"
+#include "prtime.h"
+
+namespace mozilla::dom {
+
+static LazyLogModule gGCLocationLog("GeoclueLocation");
+
+#define GCL_LOG(level, ...) \
+ MOZ_LOG(gGCLocationLog, mozilla::LogLevel::level, (__VA_ARGS__))
+
+static const char* const kGeoclueBusName = "org.freedesktop.GeoClue2";
+static const char* const kGCManagerPath = "/org/freedesktop/GeoClue2/Manager";
+static const char* const kGCManagerInterface =
+ "org.freedesktop.GeoClue2.Manager";
+static const char* const kGCClientInterface = "org.freedesktop.GeoClue2.Client";
+static const char* const kGCLocationInterface =
+ "org.freedesktop.GeoClue2.Location";
+static const char* const kDBPropertySetMethod =
+ "org.freedesktop.DBus.Properties.Set";
+
+/*
+ * Minimum altitude reported as valid (in meters),
+ * https://en.wikipedia.org/wiki/List_of_places_on_land_with_elevations_below_sea_level
+ * says that lowest land in the world is at -430 m, so let's use -500 m here.
+ */
+static const double kGCMinAlt = -500;
+
+/*
+ * Matches "enum GClueAccuracyLevel" values, see:
+ * https://www.freedesktop.org/software/geoclue/docs/geoclue-gclue-enums.html#GClueAccuracyLevel
+ */
+enum class GCAccuracyLevel {
+ None = 0,
+ Country = 1,
+ City = 4,
+ Neighborhood = 5,
+ Street = 6,
+ Exact = 8,
+};
+
+/*
+ * Whether to reuse D-Bus proxies between uses of this provider.
+ * Usually a good thing, can be disabled for debug purposes.
+ */
+static const bool kGCReuseDBusProxy = true;
+
+class GCLocProviderPriv final : public nsIGeolocationProvider,
+ public SupportsWeakPtr {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGEOLOCATIONPROVIDER
+
+ GCLocProviderPriv();
+
+ void UpdateLastPosition();
+
+ private:
+ class LocationTimerCallback final : public nsITimerCallback, public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ explicit LocationTimerCallback(GCLocProviderPriv* aParent)
+ : mParent(aParent) {}
+
+ NS_IMETHOD GetName(nsACString& aName) override {
+ aName.AssignLiteral("GCLocProvider::LocationTimerCallback");
+ return NS_OK;
+ }
+
+ private:
+ ~LocationTimerCallback() = default;
+ WeakPtr<GCLocProviderPriv> mParent;
+ };
+
+ enum class Accuracy { Unset, Low, High };
+ // States:
+ // Uninit: The default / initial state, with no client proxy yet.
+ // Initing: Takes care of establishing the client connection (GetClient /
+ // ConnectClient / SetDesktopID).
+ // SettingAccuracy: Does SetAccuracy operation, knows it should just go idle
+ // after finishing it.
+ // SettingAccuracyForStart: Does SetAccuracy operation, knows it then needs
+ // to do a Start operation after finishing it.
+ // Idle: Fully initialized, but not running state (quiescent).
+ // Starting: Starts the client by calling the Start D-Bus method.
+ // Started: Normal running state.
+ // Stopping: Stops the client by calling the Stop D-Bus method, knows it
+ // should just go idle after finishing it.
+ // StoppingForRestart: Stops the client by calling the Stop D-Bus method as
+ // a part of a Stop -> Start sequence (with possibly
+ // an accuracy update between these method calls).
+ //
+ // Valid state transitions are:
+ // (any state) -> Uninit: Transition when a D-Bus call failed or
+ // provided invalid data.
+ //
+ // Watch() startup path:
+ // Uninit -> Initing: Transition after getting the very first Watch()
+ // request
+ // or any such request while not having the client proxy.
+ // Initing -> SettingAccuracyForStart: Transition after getting a successful
+ // SetDesktopID response.
+ // SettingAccuracyForStart -> Starting: Transition after getting a
+ // successful
+ // SetAccuracy response.
+ // Idle -> Starting: Transition after getting a Watch() request while in
+ // fully
+ // initialized, but not running state.
+ // SettingAccuracy -> SettingAccuracyForStart: Transition after getting a
+ // Watch()
+ // request in the middle of
+ // setting accuracy during idle
+ // status.
+ // Stopping -> StoppingForRestart: Transition after getting a Watch()
+ // request
+ // in the middle of doing a Stop D-Bus call
+ // for idle status.
+ // StoppingForRestart -> Starting: Transition after getting a successful
+ // Stop response as a part of a Stop ->
+ // Start sequence while the previously set
+ // accuracy is still correct.
+ // StoppingForRestart -> SettingAccuracyForStart: Transition after getting
+ // a successful Stop response
+ // as a part of a Stop ->
+ // Start sequence but the set
+ // accuracy needs updating.
+ // Starting -> Started: Transition after getting a successful Start
+ // response.
+ //
+ // Shutdown() path:
+ // (any state) -> Uninit: Transition when not reusing the client proxy for
+ // any reason.
+ // Started -> Stopping: Transition from normal running state when reusing
+ // the client proxy.
+ // SettingAccuracyForStart -> SettingAccuracy: Transition when doing
+ // a shutdown in the middle of
+ // setting accuracy for a start
+ // when reusing the client
+ // proxy.
+ // SettingAccuracy -> Idle: Transition after getting a successful
+ // SetAccuracy
+ // response.
+ // StoppingForRestart -> Stopping: Transition when doing shutdown
+ // in the middle of a Stop -> Start sequence
+ // when reusing the client proxy.
+ // Stopping -> Idle: Transition after getting a successful Stop response.
+ //
+ // SetHighAccuracy() path:
+ // Started -> StoppingForRestart: Transition when accuracy needs updating
+ // on a running client.
+ // (the rest of the flow in StoppingForRestart state is the same as when
+ // being in this state in the Watch() startup path)
+ enum class ClientState {
+ Uninit,
+ Initing,
+ SettingAccuracy,
+ SettingAccuracyForStart,
+ Idle,
+ Starting,
+ Started,
+ Stopping,
+ StoppingForRestart
+ };
+
+ ~GCLocProviderPriv();
+
+ static bool AlwaysHighAccuracy();
+
+ void SetState(ClientState aNewState, const char* aNewStateStr);
+
+ void Update(nsIDOMGeoPosition* aPosition);
+ MOZ_CAN_RUN_SCRIPT void NotifyError(int aError);
+ MOZ_CAN_RUN_SCRIPT void DBusProxyError(const GError* aGError,
+ bool aResetManager = false);
+
+ MOZ_CAN_RUN_SCRIPT static void GetClientResponse(GDBusProxy* aProxy,
+ GAsyncResult* aResult,
+ gpointer aUserData);
+ void ConnectClient(const gchar* aClientPath);
+ MOZ_CAN_RUN_SCRIPT static void ConnectClientResponse(GObject* aObject,
+ GAsyncResult* aResult,
+ gpointer aUserData);
+ void SetDesktopID();
+ MOZ_CAN_RUN_SCRIPT static void SetDesktopIDResponse(GDBusProxy* aProxy,
+ GAsyncResult* aResult,
+ gpointer aUserData);
+ void SetAccuracy();
+ MOZ_CAN_RUN_SCRIPT static void SetAccuracyResponse(GDBusProxy* aProxy,
+ GAsyncResult* aResult,
+ gpointer aUserData);
+ void StartClient();
+ MOZ_CAN_RUN_SCRIPT static void StartClientResponse(GDBusProxy* aProxy,
+ GAsyncResult* aResult,
+ gpointer aUserData);
+ void StopClient(bool aForRestart);
+ MOZ_CAN_RUN_SCRIPT static void StopClientResponse(GDBusProxy* aProxy,
+ GAsyncResult* aResult,
+ gpointer aUserData);
+ void StopClientNoWait();
+ void MaybeRestartForAccuracy();
+
+ MOZ_CAN_RUN_SCRIPT static void GCManagerOwnerNotify(GObject* aObject,
+ GParamSpec* aPSpec,
+ gpointer aUserData);
+
+ static void GCClientSignal(GDBusProxy* aProxy, gchar* aSenderName,
+ gchar* aSignalName, GVariant* aParameters,
+ gpointer aUserData);
+ void ConnectLocation(const gchar* aLocationPath);
+ static bool GetLocationProperty(GDBusProxy* aProxyLocation,
+ const gchar* aName, double* aOut);
+ static void ConnectLocationResponse(GObject* aObject, GAsyncResult* aResult,
+ gpointer aUserData);
+
+ void SetLocationTimer();
+ void StopLocationTimer();
+
+ bool InDBusCall();
+ bool InDBusStoppingCall();
+ bool InDBusStoppedCall();
+
+ void DeleteManager();
+ void DoShutdown(bool aDeleteClient, bool aDeleteManager);
+ void DoShutdownClearCallback(bool aDestroying);
+
+ nsresult FallbackToMLS();
+ void StopMLSFallback();
+
+ void WatchStart();
+
+ Accuracy mAccuracyWanted = Accuracy::Unset;
+ Accuracy mAccuracySet = Accuracy::Unset;
+ RefPtr<GDBusProxy> mProxyManager;
+ RefPtr<GDBusProxy> mProxyClient;
+ RefPtr<GCancellable> mCancellable;
+ nsCOMPtr<nsIGeolocationUpdate> mCallback;
+ ClientState mClientState = ClientState::Uninit;
+ RefPtr<nsIDOMGeoPosition> mLastPosition;
+ RefPtr<nsITimer> mLocationTimer;
+ RefPtr<MLSFallback> mMLSFallback;
+};
+
+//
+// GCLocProviderPriv
+//
+
+#define GCLP_SETSTATE(this, state) this->SetState(ClientState::state, #state)
+
+GCLocProviderPriv::GCLocProviderPriv() {
+ if (AlwaysHighAccuracy()) {
+ mAccuracyWanted = Accuracy::High;
+ } else {
+ mAccuracyWanted = Accuracy::Low;
+ }
+}
+
+GCLocProviderPriv::~GCLocProviderPriv() { DoShutdownClearCallback(true); }
+
+bool GCLocProviderPriv::AlwaysHighAccuracy() {
+ return StaticPrefs::geo_provider_geoclue_always_high_accuracy();
+}
+
+void GCLocProviderPriv::SetState(ClientState aNewState,
+ const char* aNewStateStr) {
+ if (mClientState == aNewState) {
+ return;
+ }
+
+ GCL_LOG(Debug, "changing state to %s", aNewStateStr);
+ mClientState = aNewState;
+}
+
+void GCLocProviderPriv::Update(nsIDOMGeoPosition* aPosition) {
+ if (!mCallback) {
+ return;
+ }
+
+ mCallback->Update(aPosition);
+}
+
+void GCLocProviderPriv::UpdateLastPosition() {
+ MOZ_DIAGNOSTIC_ASSERT(mLastPosition, "No last position to update");
+ StopLocationTimer();
+ Update(mLastPosition);
+}
+
+nsresult GCLocProviderPriv::FallbackToMLS() {
+ GCL_LOG(Debug, "trying to fall back to MLS");
+ StopMLSFallback();
+
+ RefPtr fallback = new MLSFallback(0);
+ MOZ_TRY(fallback->Startup(mCallback));
+
+ GCL_LOG(Debug, "Started up MLS fallback");
+ mMLSFallback = std::move(fallback);
+ return NS_OK;
+}
+
+void GCLocProviderPriv::StopMLSFallback() {
+ if (!mMLSFallback) {
+ return;
+ }
+ GCL_LOG(Debug, "Clearing MLS fallback");
+ if (mMLSFallback) {
+ mMLSFallback->Shutdown();
+ mMLSFallback = nullptr;
+ }
+}
+
+void GCLocProviderPriv::NotifyError(int aError) {
+ if (!mCallback) {
+ return;
+ }
+
+ // We errored out, try to fall back to MLS.
+ if (NS_SUCCEEDED(FallbackToMLS())) {
+ return;
+ }
+
+ nsCOMPtr callback = mCallback;
+ callback->NotifyError(aError);
+}
+
+void GCLocProviderPriv::DBusProxyError(const GError* aGError,
+ bool aResetManager) {
+ // that G_DBUS_ERROR below is actually a function call, not a constant
+ GQuark gdbusDomain = G_DBUS_ERROR;
+ int error = GeolocationPositionError_Binding::POSITION_UNAVAILABLE;
+ if (aGError) {
+ if (g_error_matches(aGError, gdbusDomain, G_DBUS_ERROR_TIMEOUT) ||
+ g_error_matches(aGError, gdbusDomain, G_DBUS_ERROR_TIMED_OUT)) {
+ error = GeolocationPositionError_Binding::TIMEOUT;
+ } else if (g_error_matches(aGError, gdbusDomain,
+ G_DBUS_ERROR_LIMITS_EXCEEDED) ||
+ g_error_matches(aGError, gdbusDomain,
+ G_DBUS_ERROR_ACCESS_DENIED) ||
+ g_error_matches(aGError, gdbusDomain,
+ G_DBUS_ERROR_AUTH_FAILED)) {
+ error = GeolocationPositionError_Binding::PERMISSION_DENIED;
+ }
+ }
+
+ DoShutdown(true, aResetManager);
+ NotifyError(error);
+}
+
+void GCLocProviderPriv::GetClientResponse(GDBusProxy* aProxy,
+ GAsyncResult* aResult,
+ gpointer aUserData) {
+ GUniquePtr<GError> error;
+ RefPtr<GVariant> variant = dont_AddRef(
+ g_dbus_proxy_call_finish(aProxy, aResult, getter_Transfers(error)));
+ if (!variant) {
+ // if cancelled |self| might no longer be there
+ if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ GCL_LOG(Error, "Failed to get client: %s\n", error->message);
+ RefPtr self = static_cast<GCLocProviderPriv*>(aUserData);
+ self->DBusProxyError(error.get(), true);
+ }
+ return;
+ }
+
+ RefPtr self = static_cast<GCLocProviderPriv*>(aUserData);
+ MOZ_DIAGNOSTIC_ASSERT(self->mClientState == ClientState::Initing,
+ "Client in a wrong state");
+
+ auto signalError = MakeScopeExit([&]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ self->DBusProxyError(nullptr, true);
+ });
+
+ if (!g_variant_is_of_type(variant, G_VARIANT_TYPE_TUPLE)) {
+ GCL_LOG(Error, "Unexpected get client call return type: %s\n",
+ g_variant_get_type_string(variant));
+ return;
+ }
+
+ if (g_variant_n_children(variant) < 1) {
+ GCL_LOG(Error,
+ "Not enough params in get client call return: %" G_GSIZE_FORMAT
+ "\n",
+ g_variant_n_children(variant));
+ return;
+ }
+
+ variant = dont_AddRef(g_variant_get_child_value(variant, 0));
+ if (!g_variant_is_of_type(variant, G_VARIANT_TYPE_OBJECT_PATH)) {
+ GCL_LOG(Error, "Unexpected get client call return type inside tuple: %s\n",
+ g_variant_get_type_string(variant));
+ return;
+ }
+
+ const gchar* clientPath = g_variant_get_string(variant, nullptr);
+ GCL_LOG(Debug, "Client path: %s\n", clientPath);
+
+ signalError.release();
+ self->ConnectClient(clientPath);
+}
+
+void GCLocProviderPriv::ConnectClient(const gchar* aClientPath) {
+ MOZ_DIAGNOSTIC_ASSERT(mClientState == ClientState::Initing,
+ "Client in a wrong state");
+ MOZ_ASSERT(mCancellable, "Watch() wasn't successfully called");
+ g_dbus_proxy_new_for_bus(
+ G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr, kGeoclueBusName,
+ aClientPath, kGCClientInterface, mCancellable,
+ reinterpret_cast<GAsyncReadyCallback>(ConnectClientResponse), this);
+}
+
+void GCLocProviderPriv::ConnectClientResponse(GObject* aObject,
+ GAsyncResult* aResult,
+ gpointer aUserData) {
+ GUniquePtr<GError> error;
+ RefPtr<GDBusProxy> proxyClient =
+ dont_AddRef(g_dbus_proxy_new_finish(aResult, getter_Transfers(error)));
+ if (!proxyClient) {
+ // if cancelled |self| might no longer be there
+ if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ GCL_LOG(Error, "Failed to connect to client: %s\n", error->message);
+ RefPtr self = static_cast<GCLocProviderPriv*>(aUserData);
+ self->DBusProxyError(error.get());
+ }
+ return;
+ }
+ RefPtr self = static_cast<GCLocProviderPriv*>(aUserData);
+ self->mProxyClient = std::move(proxyClient);
+
+ MOZ_DIAGNOSTIC_ASSERT(self->mClientState == ClientState::Initing,
+ "Client in a wrong state");
+
+ GCL_LOG(Info, "Client interface connected\n");
+
+ g_signal_connect(self->mProxyClient, "g-signal", G_CALLBACK(GCClientSignal),
+ self);
+ self->SetDesktopID();
+}
+
+void GCLocProviderPriv::SetDesktopID() {
+ MOZ_DIAGNOSTIC_ASSERT(mClientState == ClientState::Initing,
+ "Client in a wrong state");
+ MOZ_DIAGNOSTIC_ASSERT(mProxyClient && mCancellable,
+ "Watch() wasn't successfully called");
+
+ nsAutoCString appName;
+ gAppData->GetDBusAppName(appName);
+ g_dbus_proxy_call(mProxyClient, kDBPropertySetMethod,
+ g_variant_new("(ssv)", kGCClientInterface, "DesktopId",
+ g_variant_new_string(appName.get())),
+ G_DBUS_CALL_FLAGS_NONE, -1, mCancellable,
+ reinterpret_cast<GAsyncReadyCallback>(SetDesktopIDResponse),
+ this);
+}
+
+void GCLocProviderPriv::SetDesktopIDResponse(GDBusProxy* aProxy,
+ GAsyncResult* aResult,
+ gpointer aUserData) {
+ GUniquePtr<GError> error;
+
+ RefPtr<GVariant> variant = dont_AddRef(
+ g_dbus_proxy_call_finish(aProxy, aResult, getter_Transfers(error)));
+ if (!variant) {
+ // if cancelled |self| might no longer be there
+ if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ GCL_LOG(Error, "Failed to set DesktopId: %s\n", error->message);
+ RefPtr self = static_cast<GCLocProviderPriv*>(aUserData);
+ self->DBusProxyError(error.get());
+ }
+ return;
+ }
+
+ RefPtr self = static_cast<GCLocProviderPriv*>(aUserData);
+ MOZ_DIAGNOSTIC_ASSERT(self->mClientState == ClientState::Initing,
+ "Client in a wrong state");
+
+ GCLP_SETSTATE(self, Idle);
+ self->SetAccuracy();
+}
+
+void GCLocProviderPriv::SetAccuracy() {
+ MOZ_DIAGNOSTIC_ASSERT(mClientState == ClientState::Idle,
+ "Client in a wrong state");
+ MOZ_DIAGNOSTIC_ASSERT(mProxyClient && mCancellable,
+ "Watch() wasn't successfully called");
+ MOZ_ASSERT(mAccuracyWanted != Accuracy::Unset, "Invalid accuracy");
+
+ guint32 accuracy;
+ if (mAccuracyWanted == Accuracy::High) {
+ accuracy = (guint32)GCAccuracyLevel::Exact;
+ } else {
+ accuracy = (guint32)GCAccuracyLevel::City;
+ }
+
+ mAccuracySet = mAccuracyWanted;
+ GCLP_SETSTATE(this, SettingAccuracyForStart);
+ g_dbus_proxy_call(
+ mProxyClient, kDBPropertySetMethod,
+ g_variant_new("(ssv)", kGCClientInterface, "RequestedAccuracyLevel",
+ g_variant_new_uint32(accuracy)),
+ G_DBUS_CALL_FLAGS_NONE, -1, mCancellable,
+ reinterpret_cast<GAsyncReadyCallback>(SetAccuracyResponse), this);
+}
+
+void GCLocProviderPriv::SetAccuracyResponse(GDBusProxy* aProxy,
+ GAsyncResult* aResult,
+ gpointer aUserData) {
+ GUniquePtr<GError> error;
+ RefPtr<GVariant> variant = dont_AddRef(
+ g_dbus_proxy_call_finish(aProxy, aResult, getter_Transfers(error)));
+ if (!variant) {
+ // if cancelled |self| might no longer be there
+ if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ GCL_LOG(Error, "Failed to set requested accuracy level: %s\n",
+ error->message);
+ RefPtr self = static_cast<GCLocProviderPriv*>(aUserData);
+ self->DBusProxyError(error.get());
+ }
+ return;
+ }
+
+ RefPtr self = static_cast<GCLocProviderPriv*>(aUserData);
+ MOZ_DIAGNOSTIC_ASSERT(
+ self->mClientState == ClientState::SettingAccuracyForStart ||
+ self->mClientState == ClientState::SettingAccuracy,
+ "Client in a wrong state");
+ bool wantStart = self->mClientState == ClientState::SettingAccuracyForStart;
+ GCLP_SETSTATE(self, Idle);
+
+ if (wantStart) {
+ self->StartClient();
+ }
+}
+
+void GCLocProviderPriv::StartClient() {
+ MOZ_DIAGNOSTIC_ASSERT(mClientState == ClientState::Idle,
+ "Client in a wrong state");
+ MOZ_DIAGNOSTIC_ASSERT(mProxyClient && mCancellable,
+ "Watch() wasn't successfully called");
+ GCLP_SETSTATE(this, Starting);
+ g_dbus_proxy_call(
+ mProxyClient, "Start", nullptr, G_DBUS_CALL_FLAGS_NONE, -1, mCancellable,
+ reinterpret_cast<GAsyncReadyCallback>(StartClientResponse), this);
+}
+
+void GCLocProviderPriv::StartClientResponse(GDBusProxy* aProxy,
+ GAsyncResult* aResult,
+ gpointer aUserData) {
+ GUniquePtr<GError> error;
+
+ RefPtr<GVariant> variant = dont_AddRef(
+ g_dbus_proxy_call_finish(aProxy, aResult, getter_Transfers(error)));
+ if (!variant) {
+ // if cancelled |self| might no longer be there
+ if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ GCL_LOG(Error, "Failed to start client: %s\n", error->message);
+ RefPtr self = static_cast<GCLocProviderPriv*>(aUserData);
+ /*
+ * A workaround for
+ * https://gitlab.freedesktop.org/geoclue/geoclue/-/issues/143 We need to
+ * get a new client instance once the agent finally connects to the
+ * Geoclue service, otherwise every Start request on the old client
+ * interface will be denied. We need to reconnect to the Manager interface
+ * to achieve this since otherwise GetClient call will simply return the
+ * old client instance.
+ */
+ bool resetManager = g_error_matches(error.get(), G_DBUS_ERROR,
+ G_DBUS_ERROR_ACCESS_DENIED);
+ self->DBusProxyError(error.get(), resetManager);
+ }
+ return;
+ }
+
+ RefPtr self = static_cast<GCLocProviderPriv*>(aUserData);
+ MOZ_DIAGNOSTIC_ASSERT(self->mClientState == ClientState::Starting,
+ "Client in a wrong state");
+ GCLP_SETSTATE(self, Started);
+ self->MaybeRestartForAccuracy();
+}
+
+void GCLocProviderPriv::StopClient(bool aForRestart) {
+ MOZ_DIAGNOSTIC_ASSERT(mClientState == ClientState::Started,
+ "Client in a wrong state");
+ MOZ_DIAGNOSTIC_ASSERT(mProxyClient && mCancellable,
+ "Watch() wasn't successfully called");
+
+ if (aForRestart) {
+ GCLP_SETSTATE(this, StoppingForRestart);
+ } else {
+ GCLP_SETSTATE(this, Stopping);
+ }
+
+ g_dbus_proxy_call(
+ mProxyClient, "Stop", nullptr, G_DBUS_CALL_FLAGS_NONE, -1, mCancellable,
+ reinterpret_cast<GAsyncReadyCallback>(StopClientResponse), this);
+}
+
+void GCLocProviderPriv::StopClientResponse(GDBusProxy* aProxy,
+ GAsyncResult* aResult,
+ gpointer aUserData) {
+ GUniquePtr<GError> error;
+ RefPtr<GVariant> variant = dont_AddRef(
+ g_dbus_proxy_call_finish(aProxy, aResult, getter_Transfers(error)));
+ if (!variant) {
+ // if cancelled |self| might no longer be there
+ if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ GCL_LOG(Error, "Failed to stop client: %s\n", error->message);
+ RefPtr self = static_cast<GCLocProviderPriv*>(aUserData);
+ self->DBusProxyError(error.get());
+ }
+ return;
+ }
+
+ RefPtr self = static_cast<GCLocProviderPriv*>(aUserData);
+ MOZ_DIAGNOSTIC_ASSERT(self->InDBusStoppingCall(), "Client in a wrong state");
+ bool wantRestart = self->mClientState == ClientState::StoppingForRestart;
+ GCLP_SETSTATE(self, Idle);
+
+ if (!wantRestart) {
+ return;
+ }
+
+ if (self->mAccuracyWanted != self->mAccuracySet) {
+ self->SetAccuracy();
+ } else {
+ self->StartClient();
+ }
+}
+
+void GCLocProviderPriv::StopClientNoWait() {
+ MOZ_DIAGNOSTIC_ASSERT(mProxyClient, "Watch() wasn't successfully called");
+ g_dbus_proxy_call(mProxyClient, "Stop", nullptr, G_DBUS_CALL_FLAGS_NONE, -1,
+ nullptr, nullptr, nullptr);
+}
+
+void GCLocProviderPriv::MaybeRestartForAccuracy() {
+ if (mAccuracyWanted == mAccuracySet) {
+ return;
+ }
+
+ if (mClientState != ClientState::Started) {
+ return;
+ }
+
+ // Setting a new accuracy requires restarting the client
+ StopClient(true);
+}
+
+void GCLocProviderPriv::GCManagerOwnerNotify(GObject* aObject,
+ GParamSpec* aPSpec,
+ gpointer aUserData) {
+ RefPtr self = static_cast<GCLocProviderPriv*>(aUserData);
+ GUniquePtr<gchar> managerOwner(
+ g_dbus_proxy_get_name_owner(self->mProxyManager));
+ if (!managerOwner) {
+ GCL_LOG(Info, "The Manager interface has lost its owner\n");
+ self->DBusProxyError(nullptr, true);
+ }
+}
+
+void GCLocProviderPriv::GCClientSignal(GDBusProxy* aProxy, gchar* aSenderName,
+ gchar* aSignalName,
+ GVariant* aParameters,
+ gpointer aUserData) {
+ if (g_strcmp0(aSignalName, "LocationUpdated")) {
+ return;
+ }
+
+ if (!g_variant_is_of_type(aParameters, G_VARIANT_TYPE_TUPLE)) {
+ GCL_LOG(Error, "Unexpected location updated signal params type: %s\n",
+ g_variant_get_type_string(aParameters));
+ return;
+ }
+
+ if (g_variant_n_children(aParameters) < 2) {
+ GCL_LOG(Error,
+ "Not enough params in location updated signal: %" G_GSIZE_FORMAT
+ "\n",
+ g_variant_n_children(aParameters));
+ return;
+ }
+
+ RefPtr<GVariant> variant =
+ dont_AddRef(g_variant_get_child_value(aParameters, 1));
+ if (!g_variant_is_of_type(variant, G_VARIANT_TYPE_OBJECT_PATH)) {
+ GCL_LOG(Error,
+ "Unexpected location updated signal new location path type: %s\n",
+ g_variant_get_type_string(variant));
+ return;
+ }
+
+ RefPtr self = static_cast<GCLocProviderPriv*>(aUserData);
+ const gchar* locationPath = g_variant_get_string(variant, nullptr);
+ GCL_LOG(Verbose, "New location path: %s\n", locationPath);
+ self->ConnectLocation(locationPath);
+}
+
+void GCLocProviderPriv::ConnectLocation(const gchar* aLocationPath) {
+ MOZ_ASSERT(mCancellable, "Startup() wasn't successfully called");
+ g_dbus_proxy_new_for_bus(
+ G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr, kGeoclueBusName,
+ aLocationPath, kGCLocationInterface, mCancellable,
+ reinterpret_cast<GAsyncReadyCallback>(ConnectLocationResponse), this);
+}
+
+bool GCLocProviderPriv::GetLocationProperty(GDBusProxy* aProxyLocation,
+ const gchar* aName, double* aOut) {
+ RefPtr<GVariant> property =
+ dont_AddRef(g_dbus_proxy_get_cached_property(aProxyLocation, aName));
+ if (!g_variant_is_of_type(property, G_VARIANT_TYPE_DOUBLE)) {
+ GCL_LOG(Error, "Unexpected location property %s type: %s\n", aName,
+ g_variant_get_type_string(property));
+ return false;
+ }
+
+ *aOut = g_variant_get_double(property);
+ return true;
+}
+
+void GCLocProviderPriv::ConnectLocationResponse(GObject* aObject,
+ GAsyncResult* aResult,
+ gpointer aUserData) {
+ GUniquePtr<GError> error;
+ RefPtr<GDBusProxy> proxyLocation =
+ dont_AddRef(g_dbus_proxy_new_finish(aResult, getter_Transfers(error)));
+ if (!proxyLocation) {
+ if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ GCL_LOG(Warning, "Failed to connect to location: %s\n", error->message);
+ }
+ return;
+ }
+
+ RefPtr self = static_cast<GCLocProviderPriv*>(aUserData);
+ /*
+ * nsGeoPositionCoords will convert NaNs to null for optional properties of
+ * the JavaScript Coordinates object.
+ */
+ double lat = UnspecifiedNaN<double>();
+ double lon = UnspecifiedNaN<double>();
+ double alt = UnspecifiedNaN<double>();
+ double hError = UnspecifiedNaN<double>();
+ const double vError = UnspecifiedNaN<double>();
+ double heading = UnspecifiedNaN<double>();
+ double speed = UnspecifiedNaN<double>();
+ struct {
+ const gchar* name;
+ double* out;
+ } props[] = {
+ {"Latitude", &lat}, {"Longitude", &lon}, {"Altitude", &alt},
+ {"Accuracy", &hError}, {"Heading", &heading}, {"Speed", &speed},
+ };
+
+ for (auto& prop : props) {
+ if (!GetLocationProperty(proxyLocation, prop.name, prop.out)) {
+ return;
+ }
+ }
+
+ if (alt < kGCMinAlt) {
+ alt = UnspecifiedNaN<double>();
+ }
+ if (speed < 0) {
+ speed = UnspecifiedNaN<double>();
+ }
+ if (heading < 0 || std::isnan(speed) || speed == 0) {
+ heading = UnspecifiedNaN<double>();
+ }
+
+ GCL_LOG(Info, "New location: %f %f +-%fm @ %gm; hdg %f spd %fm/s\n", lat, lon,
+ hError, alt, heading, speed);
+
+ self->mLastPosition =
+ new nsGeoPosition(lat, lon, alt, hError, vError, heading, speed,
+ PR_Now() / PR_USEC_PER_MSEC);
+ self->UpdateLastPosition();
+}
+
+void GCLocProviderPriv::SetLocationTimer() {
+ MOZ_DIAGNOSTIC_ASSERT(mLastPosition, "no last position to report");
+
+ StopLocationTimer();
+
+ RefPtr<LocationTimerCallback> timerCallback = new LocationTimerCallback(this);
+ NS_NewTimerWithCallback(getter_AddRefs(mLocationTimer), timerCallback, 1000,
+ nsITimer::TYPE_ONE_SHOT);
+}
+
+void GCLocProviderPriv::StopLocationTimer() {
+ if (!mLocationTimer) {
+ return;
+ }
+
+ mLocationTimer->Cancel();
+ mLocationTimer = nullptr;
+}
+
+// Did we made some D-Bus call and are still waiting for its response?
+bool GCLocProviderPriv::InDBusCall() {
+ return mClientState == ClientState::Initing ||
+ mClientState == ClientState::SettingAccuracy ||
+ mClientState == ClientState::SettingAccuracyForStart ||
+ mClientState == ClientState::Starting ||
+ mClientState == ClientState::Stopping ||
+ mClientState == ClientState::StoppingForRestart;
+}
+
+bool GCLocProviderPriv::InDBusStoppingCall() {
+ return mClientState == ClientState::Stopping ||
+ mClientState == ClientState::StoppingForRestart;
+}
+
+/*
+ * Did we made some D-Bus call while stopped and
+ * are still waiting for its response?
+ */
+bool GCLocProviderPriv::InDBusStoppedCall() {
+ return mClientState == ClientState::SettingAccuracy ||
+ mClientState == ClientState::SettingAccuracyForStart;
+}
+
+void GCLocProviderPriv::DeleteManager() {
+ if (!mProxyManager) {
+ return;
+ }
+
+ g_signal_handlers_disconnect_matched(mProxyManager, G_SIGNAL_MATCH_DATA, 0, 0,
+ nullptr, nullptr, this);
+ mProxyManager = nullptr;
+}
+
+void GCLocProviderPriv::DoShutdown(bool aDeleteClient, bool aDeleteManager) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ !aDeleteManager || aDeleteClient,
+ "deleting manager proxy requires deleting client one, too");
+
+ // Invalidate the cached last position
+ StopLocationTimer();
+ mLastPosition = nullptr;
+
+ /*
+ * Do we need to delete the D-Bus proxy (or proxies)?
+ * Either because that's what our caller wanted, or because we are set to
+ * never reuse them, or because we are in a middle of some D-Bus call while
+ * having the service running (and so not being able to issue an immediate
+ * Stop call).
+ */
+ if (aDeleteClient || !kGCReuseDBusProxy ||
+ (InDBusCall() && !InDBusStoppingCall() && !InDBusStoppedCall())) {
+ if (mClientState == ClientState::Started) {
+ StopClientNoWait();
+ GCLP_SETSTATE(this, Idle);
+ }
+ if (mProxyClient) {
+ g_signal_handlers_disconnect_matched(mProxyClient, G_SIGNAL_MATCH_DATA, 0,
+ 0, nullptr, nullptr, this);
+ }
+ if (mCancellable) {
+ g_cancellable_cancel(mCancellable);
+ mCancellable = nullptr;
+ }
+ mProxyClient = nullptr;
+
+ if (aDeleteManager || !kGCReuseDBusProxy) {
+ DeleteManager();
+ }
+
+ GCLP_SETSTATE(this, Uninit);
+ } else if (mClientState == ClientState::Started) {
+ StopClient(false);
+ } else if (mClientState == ClientState::SettingAccuracyForStart) {
+ GCLP_SETSTATE(this, SettingAccuracy);
+ } else if (mClientState == ClientState::StoppingForRestart) {
+ GCLP_SETSTATE(this, Stopping);
+ }
+}
+
+void GCLocProviderPriv::DoShutdownClearCallback(bool aDestroying) {
+ mCallback = nullptr;
+ StopMLSFallback();
+ DoShutdown(aDestroying, aDestroying);
+}
+
+NS_IMPL_ISUPPORTS(GCLocProviderPriv, nsIGeolocationProvider)
+
+// nsIGeolocationProvider
+//
+
+/*
+ * The Startup() method should only succeed if Geoclue is available on D-Bus
+ * so it can be used for determining whether to continue with this geolocation
+ * provider in Geolocation.cpp
+ */
+NS_IMETHODIMP
+GCLocProviderPriv::Startup() {
+ if (mProxyManager) {
+ return NS_OK;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mClientState == ClientState::Uninit,
+ "Client in a initialized state but no manager");
+
+ GUniquePtr<GError> error;
+ mProxyManager = dont_AddRef(g_dbus_proxy_new_for_bus_sync(
+ G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr, kGeoclueBusName,
+ kGCManagerPath, kGCManagerInterface, nullptr, getter_Transfers(error)));
+ if (!mProxyManager) {
+ GCL_LOG(Info, "Cannot connect to the Manager interface: %s\n",
+ error->message);
+ return NS_ERROR_FAILURE;
+ }
+
+ g_signal_connect(mProxyManager, "notify::g-name-owner",
+ G_CALLBACK(GCManagerOwnerNotify), this);
+
+ GUniquePtr<gchar> managerOwner(g_dbus_proxy_get_name_owner(mProxyManager));
+ if (!managerOwner) {
+ GCL_LOG(Info, "The Manager interface has no owner\n");
+ DeleteManager();
+ return NS_ERROR_FAILURE;
+ }
+
+ GCL_LOG(Info, "Manager interface connected successfully\n");
+
+ return NS_OK;
+}
+
+void GCLocProviderPriv::WatchStart() {
+ if (mClientState == ClientState::Idle) {
+ StartClient();
+ } else if (mClientState == ClientState::Started) {
+ if (mLastPosition && !mLocationTimer) {
+ GCL_LOG(Verbose,
+ "Will report the existing location if new one doesn't come up\n");
+ SetLocationTimer();
+ }
+ } else if (mClientState == ClientState::SettingAccuracy) {
+ GCLP_SETSTATE(this, SettingAccuracyForStart);
+ } else if (mClientState == ClientState::Stopping) {
+ GCLP_SETSTATE(this, StoppingForRestart);
+ }
+}
+
+NS_IMETHODIMP
+GCLocProviderPriv::Watch(nsIGeolocationUpdate* aCallback) {
+ mCallback = aCallback;
+
+ if (!mCancellable) {
+ mCancellable = dont_AddRef(g_cancellable_new());
+ }
+
+ if (mClientState != ClientState::Uninit) {
+ WatchStart();
+ return NS_OK;
+ }
+
+ if (!mProxyManager) {
+ GCL_LOG(Debug, "watch request falling back to MLS");
+ return FallbackToMLS();
+ }
+
+ StopMLSFallback();
+
+ GCLP_SETSTATE(this, Initing);
+ g_dbus_proxy_call(mProxyManager, "GetClient", nullptr, G_DBUS_CALL_FLAGS_NONE,
+ -1, mCancellable,
+ reinterpret_cast<GAsyncReadyCallback>(GetClientResponse),
+ this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GCLocProviderPriv::Shutdown() {
+ DoShutdownClearCallback(false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GCLocProviderPriv::SetHighAccuracy(bool aHigh) {
+ GCL_LOG(Verbose, "Want %s accuracy\n", aHigh ? "high" : "low");
+ if (!aHigh && AlwaysHighAccuracy()) {
+ GCL_LOG(Verbose, "Forcing high accuracy due to pref\n");
+ aHigh = true;
+ }
+
+ mAccuracyWanted = aHigh ? Accuracy::High : Accuracy::Low;
+ MaybeRestartForAccuracy();
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(GCLocProviderPriv::LocationTimerCallback, nsITimerCallback,
+ nsINamed)
+
+NS_IMETHODIMP
+GCLocProviderPriv::LocationTimerCallback::Notify(nsITimer* aTimer) {
+ if (mParent) {
+ RefPtr<GCLocProviderPriv> parent(mParent);
+ parent->UpdateLastPosition();
+ }
+
+ return NS_OK;
+}
+
+GeoclueLocationProvider::GeoclueLocationProvider() {
+ mPriv = new GCLocProviderPriv;
+}
+
+// nsISupports
+//
+
+NS_IMPL_ISUPPORTS(GeoclueLocationProvider, nsIGeolocationProvider)
+
+// nsIGeolocationProvider
+//
+
+NS_IMETHODIMP
+GeoclueLocationProvider::Startup() { return mPriv->Startup(); }
+
+NS_IMETHODIMP
+GeoclueLocationProvider::Watch(nsIGeolocationUpdate* aCallback) {
+ return mPriv->Watch(aCallback);
+}
+
+NS_IMETHODIMP
+GeoclueLocationProvider::Shutdown() { return mPriv->Shutdown(); }
+
+NS_IMETHODIMP
+GeoclueLocationProvider::SetHighAccuracy(bool aHigh) {
+ return mPriv->SetHighAccuracy(aHigh);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/system/linux/GeoclueLocationProvider.h b/dom/system/linux/GeoclueLocationProvider.h
new file mode 100644
index 0000000000..908cd25e37
--- /dev/null
+++ b/dom/system/linux/GeoclueLocationProvider.h
@@ -0,0 +1,32 @@
+/*
+ * 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 GeoclueLocationProvider_h
+#define GeoclueLocationProvider_h
+
+#include "mozilla/RefPtr.h"
+#include "nsIGeolocationProvider.h"
+
+namespace mozilla::dom {
+
+class GCLocProviderPriv;
+
+class GeoclueLocationProvider final : public nsIGeolocationProvider {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGEOLOCATIONPROVIDER
+
+ GeoclueLocationProvider();
+
+ private:
+ ~GeoclueLocationProvider() = default;
+
+ RefPtr<GCLocProviderPriv> mPriv;
+};
+
+} // namespace mozilla::dom
+
+#endif /* GeoclueLocationProvider_h */
diff --git a/dom/system/linux/GpsdLocationProvider.cpp b/dom/system/linux/GpsdLocationProvider.cpp
new file mode 100644
index 0000000000..34cd23c453
--- /dev/null
+++ b/dom/system/linux/GpsdLocationProvider.cpp
@@ -0,0 +1,446 @@
+/* -*- 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 "GpsdLocationProvider.h"
+#include <errno.h>
+#include <gps.h>
+#include "MLSFallback.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/LazyIdleThread.h"
+#include "mozilla/dom/GeolocationPositionErrorBinding.h"
+#include "GeolocationPosition.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "prtime.h"
+
+namespace mozilla {
+namespace dom {
+
+//
+// MLSGeolocationUpdate
+//
+
+/**
+ * |MLSGeolocationUpdate| provides a fallback if gpsd is not supported.
+ */
+class GpsdLocationProvider::MLSGeolocationUpdate final
+ : public nsIGeolocationUpdate {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGEOLOCATIONUPDATE
+
+ explicit MLSGeolocationUpdate(nsIGeolocationUpdate* aCallback);
+
+ protected:
+ ~MLSGeolocationUpdate() = default;
+
+ private:
+ nsCOMPtr<nsIGeolocationUpdate> mCallback;
+};
+
+GpsdLocationProvider::MLSGeolocationUpdate::MLSGeolocationUpdate(
+ nsIGeolocationUpdate* aCallback)
+ : mCallback(aCallback) {
+ MOZ_ASSERT(mCallback);
+}
+
+// nsISupports
+//
+
+NS_IMPL_ISUPPORTS(GpsdLocationProvider::MLSGeolocationUpdate,
+ nsIGeolocationUpdate);
+
+// nsIGeolocationUpdate
+//
+
+NS_IMETHODIMP
+GpsdLocationProvider::MLSGeolocationUpdate::Update(
+ nsIDOMGeoPosition* aPosition) {
+ nsCOMPtr<nsIDOMGeoPositionCoords> coords;
+ aPosition->GetCoords(getter_AddRefs(coords));
+ if (!coords) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mCallback->Update(aPosition);
+}
+
+NS_IMETHODIMP
+GpsdLocationProvider::MLSGeolocationUpdate::NotifyError(uint16_t aError) {
+ return mCallback->NotifyError(aError);
+}
+
+//
+// UpdateRunnable
+//
+
+class GpsdLocationProvider::UpdateRunnable final : public Runnable {
+ public:
+ UpdateRunnable(
+ const nsMainThreadPtrHandle<GpsdLocationProvider>& aLocationProvider,
+ nsIDOMGeoPosition* aPosition)
+ : Runnable("GpsdU"),
+ mLocationProvider(aLocationProvider),
+ mPosition(aPosition) {
+ MOZ_ASSERT(mLocationProvider);
+ MOZ_ASSERT(mPosition);
+ }
+
+ // nsIRunnable
+ //
+
+ NS_IMETHOD Run() override {
+ mLocationProvider->Update(mPosition);
+ return NS_OK;
+ }
+
+ private:
+ nsMainThreadPtrHandle<GpsdLocationProvider> mLocationProvider;
+ RefPtr<nsIDOMGeoPosition> mPosition;
+};
+
+//
+// NotifyErrorRunnable
+//
+
+class GpsdLocationProvider::NotifyErrorRunnable final : public Runnable {
+ public:
+ NotifyErrorRunnable(
+ const nsMainThreadPtrHandle<GpsdLocationProvider>& aLocationProvider,
+ int aError)
+ : Runnable("GpsdNE"),
+ mLocationProvider(aLocationProvider),
+ mError(aError) {
+ MOZ_ASSERT(mLocationProvider);
+ }
+
+ // nsIRunnable
+ //
+
+ NS_IMETHOD Run() override {
+ mLocationProvider->NotifyError(mError);
+ return NS_OK;
+ }
+
+ private:
+ nsMainThreadPtrHandle<GpsdLocationProvider> mLocationProvider;
+ int mError;
+};
+
+//
+// PollRunnable
+//
+
+/**
+ * |PollRunnable| does the main work of processing GPS data received
+ * from gpsd. libgps blocks while polling, so this runnable has to be
+ * executed on it's own thread. To cancel the poll runnable, invoke
+ * |StopRunning| and |PollRunnable| will stop within a reasonable time
+ * frame.
+ */
+class GpsdLocationProvider::PollRunnable final : public Runnable {
+ public:
+ PollRunnable(
+ const nsMainThreadPtrHandle<GpsdLocationProvider>& aLocationProvider)
+ : Runnable("GpsdP"),
+ mLocationProvider(aLocationProvider),
+ mRunning(true) {
+ MOZ_ASSERT(mLocationProvider);
+ }
+
+ static bool IsSupported() {
+ return GPSD_API_MAJOR_VERSION >= 5 && GPSD_API_MAJOR_VERSION <= 12;
+ }
+
+ bool IsRunning() const { return mRunning; }
+
+ void StopRunning() { mRunning = false; }
+
+ // nsIRunnable
+ //
+
+ NS_IMETHOD Run() override {
+ int err;
+
+ switch (GPSD_API_MAJOR_VERSION) {
+ case 5 ... 12:
+ err = PollLoop5();
+ break;
+ default:
+ err = GeolocationPositionError_Binding::POSITION_UNAVAILABLE;
+ break;
+ }
+
+ if (err) {
+ NS_DispatchToMainThread(
+ MakeAndAddRef<NotifyErrorRunnable>(mLocationProvider, err));
+ }
+
+ mLocationProvider = nullptr;
+
+ return NS_OK;
+ }
+
+ protected:
+ int PollLoop5() {
+#if GPSD_API_MAJOR_VERSION >= 5 && GPSD_API_MAJOR_VERSION <= 12
+ static const int GPSD_WAIT_TIMEOUT_US =
+ 1000000; /* us to wait for GPS data */
+
+ struct gps_data_t gpsData;
+
+ auto res = gps_open(nullptr, nullptr, &gpsData);
+
+ if (res < 0) {
+ return ErrnoToError(errno);
+ }
+
+ gps_stream(&gpsData, WATCH_ENABLE | WATCH_JSON, NULL);
+
+ int err = 0;
+
+ // nsGeoPositionCoords will convert NaNs to null for optional properties of
+ // the JavaScript Coordinates object.
+ double lat = 0;
+ double lon = 0;
+ double alt = UnspecifiedNaN<double>();
+ double hError = 0;
+ double vError = UnspecifiedNaN<double>();
+ double heading = UnspecifiedNaN<double>();
+ double speed = UnspecifiedNaN<double>();
+
+ while (IsRunning()) {
+ errno = 0;
+ auto hasGpsData = gps_waiting(&gpsData, GPSD_WAIT_TIMEOUT_US);
+
+ if (errno) {
+ err = ErrnoToError(errno);
+ break;
+ }
+ if (!hasGpsData) {
+ continue; /* woke up from timeout */
+ }
+
+# if GPSD_API_MAJOR_VERSION >= 7
+ res = gps_read(&gpsData, nullptr, 0);
+# else
+
+ res = gps_read(&gpsData);
+# endif
+
+ if (res < 0) {
+ err = ErrnoToError(errno);
+ break;
+ } else if (!res) {
+ continue; /* no data available */
+ }
+
+# if GPSD_API_MAJOR_VERSION < 10
+ if (gpsData.status == STATUS_NO_FIX) {
+ continue;
+ }
+# endif
+
+ switch (gpsData.fix.mode) {
+ case MODE_3D:
+ double galt;
+
+# if GPSD_API_MAJOR_VERSION >= 9
+ galt = gpsData.fix.altMSL;
+# else
+ galt = gpsData.fix.altitude;
+# endif
+ if (!std::isnan(galt)) {
+ alt = galt;
+ }
+ [[fallthrough]];
+ case MODE_2D:
+ if (!std::isnan(gpsData.fix.latitude)) {
+ lat = gpsData.fix.latitude;
+ }
+ if (!std::isnan(gpsData.fix.longitude)) {
+ lon = gpsData.fix.longitude;
+ }
+ if (!std::isnan(gpsData.fix.epx) && !std::isnan(gpsData.fix.epy)) {
+ hError = std::max(gpsData.fix.epx, gpsData.fix.epy);
+ } else if (!std::isnan(gpsData.fix.epx)) {
+ hError = gpsData.fix.epx;
+ } else if (!std::isnan(gpsData.fix.epy)) {
+ hError = gpsData.fix.epy;
+ }
+ if (!std::isnan(gpsData.fix.epv)) {
+ vError = gpsData.fix.epv;
+ }
+ if (!std::isnan(gpsData.fix.track)) {
+ heading = gpsData.fix.track;
+ }
+ if (!std::isnan(gpsData.fix.speed)) {
+ speed = gpsData.fix.speed;
+ }
+ break;
+ default:
+ continue; // There's no useful data in this fix; continue.
+ }
+
+ NS_DispatchToMainThread(MakeAndAddRef<UpdateRunnable>(
+ mLocationProvider,
+ new nsGeoPosition(lat, lon, alt, hError, vError, heading, speed,
+ PR_Now() / PR_USEC_PER_MSEC)));
+ }
+
+ gps_stream(&gpsData, WATCH_DISABLE, NULL);
+ gps_close(&gpsData);
+
+ return err;
+#else
+ return GeolocationPositionError_Binding::POSITION_UNAVAILABLE;
+#endif // GPSD_MAJOR_API_VERSION
+ }
+
+ static int ErrnoToError(int aErrno) {
+ switch (aErrno) {
+ case EACCES:
+ [[fallthrough]];
+ case EPERM:
+ [[fallthrough]];
+ case EROFS:
+ return GeolocationPositionError_Binding::PERMISSION_DENIED;
+ case ETIME:
+ [[fallthrough]];
+ case ETIMEDOUT:
+ return GeolocationPositionError_Binding::TIMEOUT;
+ default:
+ return GeolocationPositionError_Binding::POSITION_UNAVAILABLE;
+ }
+ }
+
+ private:
+ nsMainThreadPtrHandle<GpsdLocationProvider> mLocationProvider;
+ Atomic<bool> mRunning;
+};
+
+//
+// GpsdLocationProvider
+//
+
+const uint32_t GpsdLocationProvider::GPSD_POLL_THREAD_TIMEOUT_MS = 5000;
+
+GpsdLocationProvider::GpsdLocationProvider() {}
+
+GpsdLocationProvider::~GpsdLocationProvider() {}
+
+void GpsdLocationProvider::Update(nsIDOMGeoPosition* aPosition) {
+ if (!mCallback || !mPollRunnable) {
+ return; // not initialized or already shut down
+ }
+
+ if (mMLSProvider) {
+ /* We got a location from gpsd, so let's cancel our MLS fallback. */
+ mMLSProvider->Shutdown();
+ mMLSProvider = nullptr;
+ }
+
+ mCallback->Update(aPosition);
+}
+
+void GpsdLocationProvider::NotifyError(int aError) {
+ if (!mCallback) {
+ return; // not initialized or already shut down
+ }
+
+ if (!mMLSProvider) {
+ /* With gpsd failed, we restart MLS. It will be canceled once we
+ * get another location from gpsd.
+ */
+ mMLSProvider = MakeAndAddRef<MLSFallback>();
+ mMLSProvider->Startup(new MLSGeolocationUpdate(mCallback));
+ }
+
+ mCallback->NotifyError(aError);
+}
+
+// nsISupports
+//
+
+NS_IMPL_ISUPPORTS(GpsdLocationProvider, nsIGeolocationProvider)
+
+// nsIGeolocationProvider
+//
+
+NS_IMETHODIMP
+GpsdLocationProvider::Startup() {
+ if (!PollRunnable::IsSupported()) {
+ return NS_OK; // We'll fall back to MLS.
+ }
+
+ if (mPollRunnable) {
+ return NS_OK; // already running
+ }
+
+ RefPtr<PollRunnable> pollRunnable =
+ MakeAndAddRef<PollRunnable>(nsMainThreadPtrHandle<GpsdLocationProvider>(
+ new nsMainThreadPtrHolder<GpsdLocationProvider>("GpsdLP", this)));
+
+ // Use existing poll thread...
+ RefPtr<LazyIdleThread> pollThread = mPollThread;
+
+ // ... or create a new one.
+ if (!pollThread) {
+ pollThread = MakeAndAddRef<LazyIdleThread>(GPSD_POLL_THREAD_TIMEOUT_MS,
+ "Gpsd poll thread",
+ LazyIdleThread::ManualShutdown);
+ }
+
+ auto rv = pollThread->Dispatch(pollRunnable, NS_DISPATCH_NORMAL);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mPollRunnable = pollRunnable.forget();
+ mPollThread = pollThread.forget();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GpsdLocationProvider::Watch(nsIGeolocationUpdate* aCallback) {
+ mCallback = aCallback;
+
+ /* The MLS fallback will kick in after a few seconds if gpsd
+ * doesn't provide location information within time. Once we
+ * see the first message from gpsd, the fallback will be
+ * disabled in |Update|.
+ */
+ mMLSProvider = MakeAndAddRef<MLSFallback>();
+ mMLSProvider->Startup(new MLSGeolocationUpdate(aCallback));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GpsdLocationProvider::Shutdown() {
+ if (mMLSProvider) {
+ mMLSProvider->Shutdown();
+ mMLSProvider = nullptr;
+ }
+
+ if (!mPollRunnable) {
+ return NS_OK; // not running
+ }
+
+ mPollRunnable->StopRunning();
+ mPollRunnable = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GpsdLocationProvider::SetHighAccuracy(bool aHigh) { return NS_OK; }
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/system/linux/GpsdLocationProvider.h b/dom/system/linux/GpsdLocationProvider.h
new file mode 100644
index 0000000000..544f0e4c69
--- /dev/null
+++ b/dom/system/linux/GpsdLocationProvider.h
@@ -0,0 +1,51 @@
+/* -*- 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 GpsdLocationProvider_h
+#define GpsdLocationProvider_h
+
+#include "nsCOMPtr.h"
+#include "Geolocation.h"
+#include "nsIGeolocationProvider.h"
+
+class MLSFallback;
+
+namespace mozilla {
+
+class LazyIdleThread;
+
+namespace dom {
+
+class GpsdLocationProvider final : public nsIGeolocationProvider {
+ class MLSGeolocationUpdate;
+ class NotifyErrorRunnable;
+ class PollRunnable;
+ class UpdateRunnable;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGEOLOCATIONPROVIDER
+
+ GpsdLocationProvider();
+
+ private:
+ ~GpsdLocationProvider();
+
+ void Update(nsIDOMGeoPosition* aPosition);
+ void NotifyError(int aError);
+
+ static const uint32_t GPSD_POLL_THREAD_TIMEOUT_MS;
+
+ nsCOMPtr<nsIGeolocationUpdate> mCallback;
+ RefPtr<LazyIdleThread> mPollThread;
+ RefPtr<PollRunnable> mPollRunnable;
+ RefPtr<MLSFallback> mMLSProvider;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* GpsLocationProvider_h */
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
diff --git a/dom/system/linux/PortalLocationProvider.h b/dom/system/linux/PortalLocationProvider.h
new file mode 100644
index 0000000000..e7ead0ab5c
--- /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 <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 */
diff --git a/dom/system/linux/moz.build b/dom/system/linux/moz.build
new file mode 100644
index 0000000000..9e7d6ef31d
--- /dev/null
+++ b/dom/system/linux/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+if CONFIG["MOZ_GPSD"]:
+ SOURCES += ["GpsdLocationProvider.cpp"]
+
+ CXXFLAGS += CONFIG["MOZ_GPSD_CFLAGS"]
+
+ OS_LIBS += CONFIG["MOZ_GPSD_LIBS"]
+
+ LOCAL_INCLUDES += ["/dom/geolocation"]
+
+
+if CONFIG["MOZ_ENABLE_DBUS"]:
+ SOURCES += ["GeoclueLocationProvider.cpp"]
+ SOURCES += ["PortalLocationProvider.cpp"]
+ LOCAL_INCLUDES += ["/dom/geolocation"]
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+
+FINAL_LIBRARY = "xul"