Bug 1839170 - Refactor Snap pulling, Add Firefox Snap Core22 and GNOME 42 SDK symbols...
[gecko.git] / dom / system / linux / PortalLocationProvider.cpp
blob6ebb1854dcce975312243de271c6d5afbaabcb49
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "PortalLocationProvider.h"
8 #include "MLSFallback.h"
9 #include "mozilla/FloatingPoint.h"
10 #include "mozilla/Logging.h"
11 #include "mozilla/dom/GeolocationPositionErrorBinding.h"
12 #include "GeolocationPosition.h"
13 #include "prtime.h"
14 #include "mozilla/GUniquePtr.h"
15 #include "mozilla/UniquePtrExtensions.h"
16 #include "mozilla/XREAppData.h"
18 #include <gio/gio.h>
19 #include <glib-object.h>
21 extern const mozilla::XREAppData* gAppData;
23 namespace mozilla::dom {
25 #ifdef MOZ_LOGGING
26 static LazyLogModule sPortalLog("Portal");
27 # define LOG_PORTAL(...) MOZ_LOG(sPortalLog, LogLevel::Debug, (__VA_ARGS__))
28 #else
29 # define LOG_PORTAL(...)
30 #endif /* MOZ_LOGGING */
32 const char kDesktopBusName[] = "org.freedesktop.portal.Desktop";
33 const char kSessionInterfaceName[] = "org.freedesktop.portal.Session";
35 /**
36 * |MLSGeolocationUpdate| provides a fallback if Portal is not supported.
38 class PortalLocationProvider::MLSGeolocationUpdate final
39 : public nsIGeolocationUpdate {
40 public:
41 NS_DECL_ISUPPORTS
42 NS_DECL_NSIGEOLOCATIONUPDATE
44 explicit MLSGeolocationUpdate(nsIGeolocationUpdate* aCallback);
46 protected:
47 ~MLSGeolocationUpdate() = default;
49 private:
50 const nsCOMPtr<nsIGeolocationUpdate> mCallback;
53 PortalLocationProvider::MLSGeolocationUpdate::MLSGeolocationUpdate(
54 nsIGeolocationUpdate* aCallback)
55 : mCallback(aCallback) {
56 MOZ_ASSERT(mCallback);
59 NS_IMPL_ISUPPORTS(PortalLocationProvider::MLSGeolocationUpdate,
60 nsIGeolocationUpdate);
62 // nsIGeolocationUpdate
65 NS_IMETHODIMP
66 PortalLocationProvider::MLSGeolocationUpdate::Update(
67 nsIDOMGeoPosition* aPosition) {
68 nsCOMPtr<nsIDOMGeoPositionCoords> coords;
69 aPosition->GetCoords(getter_AddRefs(coords));
70 if (!coords) {
71 return NS_ERROR_FAILURE;
73 LOG_PORTAL("MLS is updating position\n");
74 return mCallback->Update(aPosition);
77 NS_IMETHODIMP
78 PortalLocationProvider::MLSGeolocationUpdate::NotifyError(uint16_t aError) {
79 nsCOMPtr<nsIGeolocationUpdate> callback(mCallback);
80 return callback->NotifyError(aError);
84 // PortalLocationProvider
87 PortalLocationProvider::PortalLocationProvider() = default;
89 PortalLocationProvider::~PortalLocationProvider() {
90 if (mDBUSLocationProxy || mRefreshTimer || mMLSProvider) {
91 NS_WARNING(
92 "PortalLocationProvider: Shutdown() had not been called before "
93 "destructor.");
94 Shutdown();
98 void PortalLocationProvider::Update(nsIDOMGeoPosition* aPosition) {
99 if (!mCallback) {
100 return; // not initialized or already shut down
103 if (mMLSProvider) {
104 LOG_PORTAL(
105 "Update from location portal received: Cancelling fallback MLS "
106 "provider\n");
107 mMLSProvider->Shutdown();
108 mMLSProvider = nullptr;
111 LOG_PORTAL("Send updated location to the callback %p", mCallback.get());
112 mCallback->Update(aPosition);
114 aPosition->GetCoords(getter_AddRefs(mLastGeoPositionCoords));
115 // Schedule sending repetitive updates because we don't get more until
116 // position is changed from portal. That would lead to timeout on the
117 // Firefox side.
118 SetRefreshTimer(5000);
121 void PortalLocationProvider::NotifyError(int aError) {
122 LOG_PORTAL("*****NotifyError %d\n", aError);
123 if (!mCallback) {
124 return; // not initialized or already shut down
127 if (!mMLSProvider) {
128 /* With Portal failed, we restart MLS. It will be canceled once we
129 * get another location from Portal. Start it immediately.
131 mMLSProvider = MakeAndAddRef<MLSFallback>(0);
132 mMLSProvider->Startup(new MLSGeolocationUpdate(mCallback));
135 nsCOMPtr<nsIGeolocationUpdate> callback(mCallback);
136 callback->NotifyError(aError);
139 NS_IMPL_ISUPPORTS(PortalLocationProvider, nsIGeolocationProvider)
141 static void location_updated_signal_cb(GDBusProxy* proxy, gchar* sender_name,
142 gchar* signal_name, GVariant* parameters,
143 gpointer user_data) {
144 LOG_PORTAL("Signal: %s received from: %s\n", sender_name, signal_name);
146 if (g_strcmp0(signal_name, "LocationUpdated")) {
147 LOG_PORTAL("Unexpected signal %s received", signal_name);
148 return;
151 auto* locationProvider = static_cast<PortalLocationProvider*>(user_data);
152 RefPtr<GVariant> response_data;
153 gchar* session_handle;
154 g_variant_get(parameters, "(o@a{sv})", &session_handle,
155 response_data.StartAssignment());
156 if (!response_data) {
157 LOG_PORTAL("Missing response data from portal\n");
158 locationProvider->NotifyError(
159 GeolocationPositionError_Binding::POSITION_UNAVAILABLE);
160 return;
162 LOG_PORTAL("Session handle: %s Response data: %s\n", session_handle,
163 GUniquePtr<gchar>(g_variant_print(response_data, TRUE)).get());
164 g_free(session_handle);
166 double lat = 0;
167 double lon = 0;
168 if (!g_variant_lookup(response_data, "Latitude", "d", &lat) ||
169 !g_variant_lookup(response_data, "Longitude", "d", &lon)) {
170 locationProvider->NotifyError(
171 GeolocationPositionError_Binding::POSITION_UNAVAILABLE);
172 return;
175 double alt = UnspecifiedNaN<double>();
176 g_variant_lookup(response_data, "Altitude", "d", &alt);
177 double vError = 0;
178 double hError = UnspecifiedNaN<double>();
179 g_variant_lookup(response_data, "Accuracy", "d", &hError);
180 double heading = UnspecifiedNaN<double>();
181 g_variant_lookup(response_data, "Heading", "d", &heading);
182 double speed = UnspecifiedNaN<double>();
183 g_variant_lookup(response_data, "Speed", "d", &speed);
185 locationProvider->Update(new nsGeoPosition(lat, lon, alt, hError, vError,
186 heading, speed,
187 PR_Now() / PR_USEC_PER_MSEC));
190 NS_IMETHODIMP
191 PortalLocationProvider::Startup() {
192 LOG_PORTAL("Starting location portal");
193 if (mDBUSLocationProxy) {
194 LOG_PORTAL("Proxy already started.\n");
195 return NS_OK;
198 // Create dbus proxy for the Location portal
199 GUniquePtr<GError> error;
200 mDBUSLocationProxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync(
201 G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE,
202 nullptr, /* GDBusInterfaceInfo */
203 kDesktopBusName, "/org/freedesktop/portal/desktop",
204 "org.freedesktop.portal.Location", nullptr, /* GCancellable */
205 getter_Transfers(error)));
206 if (!mDBUSLocationProxy) {
207 g_printerr("Error creating location dbus proxy: %s\n", error->message);
208 return NS_OK; // fallback to MLS
211 // Listen to signals which will be send to us with the location data
212 mDBUSSignalHandler =
213 g_signal_connect(mDBUSLocationProxy, "g-signal",
214 G_CALLBACK(location_updated_signal_cb), this);
216 // Call CreateSession of the location portal
217 GVariantBuilder builder;
219 nsAutoCString appName;
220 gAppData->GetDBusAppName(appName);
221 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
222 g_variant_builder_add(&builder, "{sv}", "session_handle_token",
223 g_variant_new_string(appName.get()));
225 RefPtr<GVariant> result = dont_AddRef(g_dbus_proxy_call_sync(
226 mDBUSLocationProxy, "CreateSession", g_variant_new("(a{sv})", &builder),
227 G_DBUS_CALL_FLAGS_NONE, -1, nullptr, getter_Transfers(error)));
229 g_variant_builder_clear(&builder);
231 if (!result) {
232 g_printerr("Error calling CreateSession method: %s\n", error->message);
233 return NS_OK; // fallback to MLS
236 // Start to listen to the location changes
237 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
239 // TODO Use wayland:handle as described in
240 // https://flatpak.github.io/xdg-desktop-portal/#parent_window
241 const gchar* parent_window = "";
242 gchar* portalSession;
243 g_variant_get_child(result, 0, "o", &portalSession);
244 mPortalSession.reset(portalSession);
246 result = g_dbus_proxy_call_sync(
247 mDBUSLocationProxy, "Start",
248 g_variant_new("(osa{sv})", mPortalSession.get(), parent_window, &builder),
249 G_DBUS_CALL_FLAGS_NONE, -1, nullptr, getter_Transfers(error));
251 g_variant_builder_clear(&builder);
253 if (!result) {
254 g_printerr("Error calling Start method: %s\n", error->message);
255 return NS_OK; // fallback to MLS
257 return NS_OK;
260 NS_IMETHODIMP
261 PortalLocationProvider::Watch(nsIGeolocationUpdate* aCallback) {
262 mCallback = aCallback;
264 if (mLastGeoPositionCoords) {
265 // We cannot immediately call the Update there becase the window is not
266 // yet ready for that.
267 LOG_PORTAL(
268 "Update location in 1ms because we have the valid coords cached.");
269 SetRefreshTimer(1);
270 return NS_OK;
273 /* The MLS fallback will kick in after 12 seconds if portal
274 * doesn't provide location information within time. Once we
275 * see the first message from portal, the fallback will be
276 * disabled in |Update|.
278 mMLSProvider = MakeAndAddRef<MLSFallback>(12000);
279 mMLSProvider->Startup(new MLSGeolocationUpdate(aCallback));
281 return NS_OK;
284 NS_IMETHODIMP PortalLocationProvider::GetName(nsACString& aName) {
285 aName.AssignLiteral("PortalLocationProvider");
286 return NS_OK;
289 void PortalLocationProvider::SetRefreshTimer(int aDelay) {
290 LOG_PORTAL("SetRefreshTimer for %p to %d ms\n", this, aDelay);
291 if (!mRefreshTimer) {
292 NS_NewTimerWithCallback(getter_AddRefs(mRefreshTimer), this, aDelay,
293 nsITimer::TYPE_ONE_SHOT);
294 } else {
295 mRefreshTimer->Cancel();
296 mRefreshTimer->InitWithCallback(this, aDelay, nsITimer::TYPE_ONE_SHOT);
300 NS_IMETHODIMP
301 PortalLocationProvider::Notify(nsITimer* timer) {
302 // We need to reschedule the timer because we won't get any update
303 // from portal until the location is changed. That would cause
304 // watchPosition to fail with TIMEOUT error.
305 SetRefreshTimer(5000);
306 if (mLastGeoPositionCoords) {
307 LOG_PORTAL("Update location callback with latest coords.");
308 mCallback->Update(
309 new nsGeoPosition(mLastGeoPositionCoords, PR_Now() / PR_USEC_PER_MSEC));
311 return NS_OK;
314 NS_IMETHODIMP
315 PortalLocationProvider::Shutdown() {
316 LOG_PORTAL("Shutdown location provider");
317 if (mRefreshTimer) {
318 mRefreshTimer->Cancel();
319 mRefreshTimer = nullptr;
321 mLastGeoPositionCoords = nullptr;
322 if (mDBUSLocationProxy) {
323 g_signal_handler_disconnect(mDBUSLocationProxy, mDBUSSignalHandler);
324 LOG_PORTAL("calling Close method to the session interface...\n");
325 RefPtr<GDBusMessage> message = dont_AddRef(g_dbus_message_new_method_call(
326 kDesktopBusName, mPortalSession.get(), kSessionInterfaceName, "Close"));
327 mPortalSession = nullptr;
328 if (message) {
329 GUniquePtr<GError> error;
330 GDBusConnection* connection =
331 g_dbus_proxy_get_connection(mDBUSLocationProxy);
332 g_dbus_connection_send_message(
333 connection, message, G_DBUS_SEND_MESSAGE_FLAGS_NONE,
334 /*out_serial=*/nullptr, getter_Transfers(error));
335 if (error) {
336 g_printerr("Failed to close the session: %s\n", error->message);
339 mDBUSLocationProxy = nullptr;
341 if (mMLSProvider) {
342 mMLSProvider->Shutdown();
343 mMLSProvider = nullptr;
345 return NS_OK;
348 NS_IMETHODIMP
349 PortalLocationProvider::SetHighAccuracy(bool aHigh) { return NS_OK; }
351 } // namespace mozilla::dom