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"
14 #include "mozilla/GUniquePtr.h"
15 #include "mozilla/UniquePtrExtensions.h"
16 #include "mozilla/XREAppData.h"
19 #include <glib-object.h>
21 extern const mozilla::XREAppData
* gAppData
;
23 namespace mozilla::dom
{
26 static LazyLogModule
sPortalLog("Portal");
27 # define LOG_PORTAL(...) MOZ_LOG(sPortalLog, LogLevel::Debug, (__VA_ARGS__))
29 # define LOG_PORTAL(...)
30 #endif /* MOZ_LOGGING */
32 const char kDesktopBusName
[] = "org.freedesktop.portal.Desktop";
33 const char kSessionInterfaceName
[] = "org.freedesktop.portal.Session";
36 * |MLSGeolocationUpdate| provides a fallback if Portal is not supported.
38 class PortalLocationProvider::MLSGeolocationUpdate final
39 : public nsIGeolocationUpdate
{
42 NS_DECL_NSIGEOLOCATIONUPDATE
44 explicit MLSGeolocationUpdate(nsIGeolocationUpdate
* aCallback
);
47 ~MLSGeolocationUpdate() = default;
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
66 PortalLocationProvider::MLSGeolocationUpdate::Update(
67 nsIDOMGeoPosition
* aPosition
) {
68 nsCOMPtr
<nsIDOMGeoPositionCoords
> coords
;
69 aPosition
->GetCoords(getter_AddRefs(coords
));
71 return NS_ERROR_FAILURE
;
73 LOG_PORTAL("MLS is updating position\n");
74 return mCallback
->Update(aPosition
);
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
) {
92 "PortalLocationProvider: Shutdown() had not been called before "
98 void PortalLocationProvider::Update(nsIDOMGeoPosition
* aPosition
) {
100 return; // not initialized or already shut down
105 "Update from location portal received: Cancelling fallback MLS "
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
118 SetRefreshTimer(5000);
121 void PortalLocationProvider::NotifyError(int aError
) {
122 LOG_PORTAL("*****NotifyError %d\n", aError
);
124 return; // not initialized or already shut down
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
);
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
);
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
);
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
);
175 double alt
= UnspecifiedNaN
<double>();
176 g_variant_lookup(response_data
, "Altitude", "d", &alt
);
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
,
187 PR_Now() / PR_USEC_PER_MSEC
));
191 PortalLocationProvider::Startup() {
192 LOG_PORTAL("Starting location portal");
193 if (mDBUSLocationProxy
) {
194 LOG_PORTAL("Proxy already started.\n");
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
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
);
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
);
254 g_printerr("Error calling Start method: %s\n", error
->message
);
255 return NS_OK
; // fallback to MLS
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.
268 "Update location in 1ms because we have the valid coords cached.");
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
));
284 NS_IMETHODIMP
PortalLocationProvider::GetName(nsACString
& aName
) {
285 aName
.AssignLiteral("PortalLocationProvider");
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
);
295 mRefreshTimer
->Cancel();
296 mRefreshTimer
->InitWithCallback(this, aDelay
, nsITimer::TYPE_ONE_SHOT
);
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.");
309 new nsGeoPosition(mLastGeoPositionCoords
, PR_Now() / PR_USEC_PER_MSEC
));
315 PortalLocationProvider::Shutdown() {
316 LOG_PORTAL("Shutdown location provider");
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;
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
));
336 g_printerr("Failed to close the session: %s\n", error
->message
);
339 mDBUSLocationProxy
= nullptr;
342 mMLSProvider
->Shutdown();
343 mMLSProvider
= nullptr;
349 PortalLocationProvider::SetHighAccuracy(bool aHigh
) { return NS_OK
; }
351 } // namespace mozilla::dom