PortalLocationProvider.cpp (11585B)
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/. */ 6 7 #include "PortalLocationProvider.h" 8 9 #include <gio/gio.h> 10 #include <glib-object.h> 11 12 #include "GeolocationPosition.h" 13 #include "MLSFallback.h" 14 #include "mozilla/FloatingPoint.h" 15 #include "mozilla/GUniquePtr.h" 16 #include "mozilla/Logging.h" 17 #include "mozilla/XREAppData.h" 18 #include "mozilla/dom/GeolocationPositionErrorBinding.h" 19 #include "nsAppShell.h" 20 #include "prtime.h" 21 22 extern const mozilla::XREAppData* gAppData; 23 24 namespace mozilla::dom { 25 26 #ifdef MOZ_LOGGING 27 static LazyLogModule sPortalLog("Portal"); 28 # define LOG_PORTAL(...) MOZ_LOG(sPortalLog, LogLevel::Debug, (__VA_ARGS__)) 29 #else 30 # define LOG_PORTAL(...) 31 #endif /* MOZ_LOGGING */ 32 33 const char kDesktopBusName[] = "org.freedesktop.portal.Desktop"; 34 const char kSessionInterfaceName[] = "org.freedesktop.portal.Session"; 35 36 /** 37 * |MLSGeolocationUpdate| provides a fallback if Portal is not supported. 38 */ 39 class PortalLocationProvider::MLSGeolocationUpdate final 40 : public nsIGeolocationUpdate { 41 public: 42 NS_DECL_ISUPPORTS 43 NS_DECL_NSIGEOLOCATIONUPDATE 44 45 explicit MLSGeolocationUpdate(nsIGeolocationUpdate* aCallback); 46 47 protected: 48 ~MLSGeolocationUpdate() = default; 49 50 private: 51 const nsCOMPtr<nsIGeolocationUpdate> mCallback; 52 }; 53 54 PortalLocationProvider::MLSGeolocationUpdate::MLSGeolocationUpdate( 55 nsIGeolocationUpdate* aCallback) 56 : mCallback(aCallback) { 57 MOZ_ASSERT(mCallback); 58 } 59 60 NS_IMPL_ISUPPORTS(PortalLocationProvider::MLSGeolocationUpdate, 61 nsIGeolocationUpdate); 62 63 // nsIGeolocationUpdate 64 // 65 66 NS_IMETHODIMP 67 PortalLocationProvider::MLSGeolocationUpdate::Update( 68 nsIDOMGeoPosition* aPosition) { 69 nsCOMPtr<nsIDOMGeoPositionCoords> coords; 70 aPosition->GetCoords(getter_AddRefs(coords)); 71 if (!coords) { 72 return NS_ERROR_FAILURE; 73 } 74 LOG_PORTAL("MLS is updating position\n"); 75 return mCallback->Update(aPosition); 76 } 77 78 NS_IMETHODIMP 79 PortalLocationProvider::MLSGeolocationUpdate::NotifyError(uint16_t aError) { 80 nsCOMPtr<nsIGeolocationUpdate> callback(mCallback); 81 return callback->NotifyError(aError); 82 } 83 84 // 85 // PortalLocationProvider 86 // 87 88 PortalLocationProvider::PortalLocationProvider() = default; 89 90 PortalLocationProvider::~PortalLocationProvider() { 91 if (mDBUSLocationProxy || mRefreshTimer || mMLSProvider) { 92 NS_WARNING( 93 "PortalLocationProvider: Shutdown() had not been called before " 94 "destructor."); 95 Shutdown(); 96 } 97 } 98 99 void PortalLocationProvider::Update(nsIDOMGeoPosition* aPosition) { 100 if (!mCallback) { 101 return; // not initialized or already shut down 102 } 103 104 if (mMLSProvider) { 105 LOG_PORTAL( 106 "Update from location portal received: Cancelling fallback MLS " 107 "provider\n"); 108 mMLSProvider->Shutdown(MLSFallback::ShutdownReason::ProviderResponded); 109 mMLSProvider = nullptr; 110 } 111 112 LOG_PORTAL("Send updated location to the callback %p", mCallback.get()); 113 mCallback->Update(aPosition); 114 115 aPosition->GetCoords(getter_AddRefs(mLastGeoPositionCoords)); 116 // Schedule sending repetitive updates because we don't get more until 117 // position is changed from portal. That would lead to timeout on the 118 // Firefox side. 119 SetRefreshTimer(5000); 120 } 121 122 void PortalLocationProvider::NotifyError(int aError) { 123 LOG_PORTAL("*****NotifyError %d\n", aError); 124 if (!mCallback) { 125 return; // not initialized or already shut down 126 } 127 128 if (!mMLSProvider) { 129 /* With Portal failed, we restart MLS. It will be canceled once we 130 * get another location from Portal. Start it immediately. 131 */ 132 mMLSProvider = MakeAndAddRef<MLSFallback>(0); 133 mMLSProvider->Startup(new MLSGeolocationUpdate(mCallback)); 134 } 135 136 nsCOMPtr<nsIGeolocationUpdate> callback(mCallback); 137 callback->NotifyError(aError); 138 } 139 140 NS_IMPL_ISUPPORTS(PortalLocationProvider, nsIGeolocationProvider) 141 142 static void location_updated_signal_cb(GDBusProxy* proxy, gchar* sender_name, 143 gchar* signal_name, GVariant* parameters, 144 gpointer user_data) { 145 LOG_PORTAL("Signal: %s received from: %s\n", sender_name, signal_name); 146 147 if (g_strcmp0(signal_name, "LocationUpdated")) { 148 LOG_PORTAL("Unexpected signal %s received", signal_name); 149 return; 150 } 151 152 auto* locationProvider = static_cast<PortalLocationProvider*>(user_data); 153 RefPtr<GVariant> response_data; 154 gchar* session_handle; 155 g_variant_get(parameters, "(o@a{sv})", &session_handle, 156 response_data.StartAssignment()); 157 if (!response_data) { 158 LOG_PORTAL("Missing response data from portal\n"); 159 locationProvider->NotifyError( 160 GeolocationPositionError_Binding::POSITION_UNAVAILABLE); 161 return; 162 } 163 LOG_PORTAL("Session handle: %s Response data: %s\n", session_handle, 164 GUniquePtr<gchar>(g_variant_print(response_data, TRUE)).get()); 165 g_free(session_handle); 166 167 double lat = 0; 168 double lon = 0; 169 if (!g_variant_lookup(response_data, "Latitude", "d", &lat) || 170 !g_variant_lookup(response_data, "Longitude", "d", &lon)) { 171 locationProvider->NotifyError( 172 GeolocationPositionError_Binding::POSITION_UNAVAILABLE); 173 return; 174 } 175 176 double alt = UnspecifiedNaN<double>(); 177 g_variant_lookup(response_data, "Altitude", "d", &alt); 178 double vError = 0; 179 double hError = UnspecifiedNaN<double>(); 180 g_variant_lookup(response_data, "Accuracy", "d", &hError); 181 double heading = UnspecifiedNaN<double>(); 182 g_variant_lookup(response_data, "Heading", "d", &heading); 183 double speed = UnspecifiedNaN<double>(); 184 g_variant_lookup(response_data, "Speed", "d", &speed); 185 186 locationProvider->Update(new nsGeoPosition(lat, lon, alt, hError, vError, 187 heading, speed, 188 PR_Now() / PR_USEC_PER_MSEC)); 189 } 190 191 NS_IMETHODIMP 192 PortalLocationProvider::Startup() { 193 LOG_PORTAL("Starting location portal"); 194 if (mDBUSLocationProxy) { 195 LOG_PORTAL("Proxy already started.\n"); 196 return NS_OK; 197 } 198 199 // Create dbus proxy for the Location portal 200 GUniquePtr<GError> error; 201 nsAppShell::DBusConnectionCheck(); 202 mDBUSLocationProxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync( 203 G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, 204 nullptr, /* GDBusInterfaceInfo */ 205 kDesktopBusName, "/org/freedesktop/portal/desktop", 206 "org.freedesktop.portal.Location", nullptr, /* GCancellable */ 207 getter_Transfers(error))); 208 if (!mDBUSLocationProxy) { 209 g_printerr("Error creating location dbus proxy: %s\n", error->message); 210 return NS_OK; // fallback to MLS 211 } 212 213 // Listen to signals which will be send to us with the location data 214 mDBUSSignalHandler = 215 g_signal_connect(mDBUSLocationProxy, "g-signal", 216 G_CALLBACK(location_updated_signal_cb), this); 217 218 // Call CreateSession of the location portal 219 GVariantBuilder builder; 220 221 nsAutoCString appName; 222 gAppData->GetDBusAppName(appName); 223 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); 224 g_variant_builder_add(&builder, "{sv}", "session_handle_token", 225 g_variant_new_string(appName.get())); 226 227 RefPtr<GVariant> result = dont_AddRef(g_dbus_proxy_call_sync( 228 mDBUSLocationProxy, "CreateSession", g_variant_new("(a{sv})", &builder), 229 G_DBUS_CALL_FLAGS_NONE, -1, nullptr, getter_Transfers(error))); 230 231 g_variant_builder_clear(&builder); 232 233 if (!result) { 234 g_printerr("Error calling CreateSession method: %s\n", error->message); 235 return NS_OK; // fallback to MLS 236 } 237 238 // Start to listen to the location changes 239 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); 240 241 // TODO Use wayland:handle as described in 242 // https://flatpak.github.io/xdg-desktop-portal/#parent_window 243 const gchar* parent_window = ""; 244 gchar* portalSession; 245 g_variant_get_child(result, 0, "o", &portalSession); 246 mPortalSession.reset(portalSession); 247 248 result = g_dbus_proxy_call_sync( 249 mDBUSLocationProxy, "Start", 250 g_variant_new("(osa{sv})", mPortalSession.get(), parent_window, &builder), 251 G_DBUS_CALL_FLAGS_NONE, -1, nullptr, getter_Transfers(error)); 252 253 g_variant_builder_clear(&builder); 254 255 if (!result) { 256 g_printerr("Error calling Start method: %s\n", error->message); 257 return NS_OK; // fallback to MLS 258 } 259 return NS_OK; 260 } 261 262 NS_IMETHODIMP 263 PortalLocationProvider::Watch(nsIGeolocationUpdate* aCallback) { 264 mCallback = aCallback; 265 266 if (mLastGeoPositionCoords) { 267 // We cannot immediately call the Update there becase the window is not 268 // yet ready for that. 269 LOG_PORTAL( 270 "Update location in 1ms because we have the valid coords cached."); 271 SetRefreshTimer(1); 272 return NS_OK; 273 } 274 275 /* The MLS fallback will kick in after 12 seconds if portal 276 * doesn't provide location information within time. Once we 277 * see the first message from portal, the fallback will be 278 * disabled in |Update|. 279 */ 280 mMLSProvider = MakeAndAddRef<MLSFallback>(12000); 281 mMLSProvider->Startup(new MLSGeolocationUpdate(aCallback)); 282 283 return NS_OK; 284 } 285 286 NS_IMETHODIMP PortalLocationProvider::GetName(nsACString& aName) { 287 aName.AssignLiteral("PortalLocationProvider"); 288 return NS_OK; 289 } 290 291 void PortalLocationProvider::SetRefreshTimer(int aDelay) { 292 LOG_PORTAL("SetRefreshTimer for %p to %d ms\n", this, aDelay); 293 if (!mRefreshTimer) { 294 NS_NewTimerWithCallback(getter_AddRefs(mRefreshTimer), this, aDelay, 295 nsITimer::TYPE_ONE_SHOT); 296 } else { 297 mRefreshTimer->Cancel(); 298 mRefreshTimer->InitWithCallback(this, aDelay, nsITimer::TYPE_ONE_SHOT); 299 } 300 } 301 302 NS_IMETHODIMP 303 PortalLocationProvider::Notify(nsITimer* timer) { 304 // We need to reschedule the timer because we won't get any update 305 // from portal until the location is changed. That would cause 306 // watchPosition to fail with TIMEOUT error. 307 SetRefreshTimer(5000); 308 if (mLastGeoPositionCoords) { 309 LOG_PORTAL("Update location callback with latest coords."); 310 mCallback->Update( 311 new nsGeoPosition(mLastGeoPositionCoords, PR_Now() / PR_USEC_PER_MSEC)); 312 } 313 return NS_OK; 314 } 315 316 NS_IMETHODIMP 317 PortalLocationProvider::Shutdown() { 318 LOG_PORTAL("Shutdown location provider"); 319 if (mRefreshTimer) { 320 mRefreshTimer->Cancel(); 321 mRefreshTimer = nullptr; 322 } 323 mLastGeoPositionCoords = nullptr; 324 if (mDBUSLocationProxy) { 325 nsAppShell::DBusConnectionCheck(); 326 g_signal_handler_disconnect(mDBUSLocationProxy, mDBUSSignalHandler); 327 LOG_PORTAL("calling Close method to the session interface...\n"); 328 RefPtr<GDBusMessage> message = dont_AddRef(g_dbus_message_new_method_call( 329 kDesktopBusName, mPortalSession.get(), kSessionInterfaceName, "Close")); 330 mPortalSession = nullptr; 331 if (message) { 332 GUniquePtr<GError> error; 333 GDBusConnection* connection = 334 g_dbus_proxy_get_connection(mDBUSLocationProxy); 335 g_dbus_connection_send_message( 336 connection, message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, 337 /*out_serial=*/nullptr, getter_Transfers(error)); 338 if (error) { 339 g_printerr("Failed to close the session: %s\n", error->message); 340 } 341 } 342 mDBUSLocationProxy = nullptr; 343 } 344 if (mMLSProvider) { 345 mMLSProvider->Shutdown(MLSFallback::ShutdownReason::ProviderShutdown); 346 mMLSProvider = nullptr; 347 } 348 return NS_OK; 349 } 350 351 NS_IMETHODIMP 352 PortalLocationProvider::SetHighAccuracy(bool aHigh) { return NS_OK; } 353 354 } // namespace mozilla::dom