GeoclueLocationProvider.cpp (39906B)
1 /* 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 * 6 * Author: Maciej S. Szmigiero <mail@maciej.szmigiero.name> 7 */ 8 9 #include "GeoclueLocationProvider.h" 10 11 #include <gio/gio.h> 12 #include <glib.h> 13 14 #include "MLSFallback.h" 15 #include "mozilla/FloatingPoint.h" 16 #include "mozilla/GRefPtr.h" 17 #include "mozilla/GUniquePtr.h" 18 #include "mozilla/Logging.h" 19 #include "mozilla/ScopeExit.h" 20 #include "mozilla/StaticPrefs_geo.h" 21 #include "mozilla/WeakPtr.h" 22 #include "mozilla/XREAppData.h" 23 #include "mozilla/dom/GeolocationPosition.h" 24 #include "mozilla/dom/GeolocationPositionErrorBinding.h" 25 #include "mozilla/glean/DomGeolocationMetrics.h" 26 #include "nsAppRunner.h" 27 #include "nsAppShell.h" 28 #include "nsCOMPtr.h" 29 #include "nsIDOMGeoPosition.h" 30 #include "nsINamed.h" 31 #include "nsITimer.h" 32 #include "nsStringFwd.h" 33 #include "prtime.h" 34 35 namespace mozilla::dom { 36 37 static LazyLogModule gGCLocationLog("GeoclueLocation"); 38 39 #define GCL_LOG(level, ...) \ 40 MOZ_LOG(gGCLocationLog, mozilla::LogLevel::level, (__VA_ARGS__)) 41 42 static const char* const kGeoclueBusName = "org.freedesktop.GeoClue2"; 43 static const char* const kGCManagerPath = "/org/freedesktop/GeoClue2/Manager"; 44 static const char* const kGCManagerInterface = 45 "org.freedesktop.GeoClue2.Manager"; 46 static const char* const kGCClientInterface = "org.freedesktop.GeoClue2.Client"; 47 static const char* const kGCLocationInterface = 48 "org.freedesktop.GeoClue2.Location"; 49 static const char* const kDBPropertySetMethod = 50 "org.freedesktop.DBus.Properties.Set"; 51 52 /* 53 * Minimum altitude reported as valid (in meters), 54 * https://en.wikipedia.org/wiki/List_of_places_on_land_with_elevations_below_sea_level 55 * says that lowest land in the world is at -430 m, so let's use -500 m here. 56 */ 57 static const double kGCMinAlt = -500; 58 59 /* 60 * Matches "enum GClueAccuracyLevel" values, see: 61 * https://www.freedesktop.org/software/geoclue/docs/geoclue-gclue-enums.html#GClueAccuracyLevel 62 */ 63 enum class GCAccuracyLevel { 64 None = 0, 65 Country = 1, 66 City = 4, 67 Neighborhood = 5, 68 Street = 6, 69 Exact = 8, 70 }; 71 72 /* 73 * Whether to reuse D-Bus proxies between uses of this provider. 74 * Usually a good thing, can be disabled for debug purposes. 75 */ 76 static const bool kGCReuseDBusProxy = true; 77 78 class GCLocProviderPriv final : public nsIGeolocationProvider, 79 public SupportsWeakPtr { 80 public: 81 NS_DECL_ISUPPORTS 82 NS_DECL_NSIGEOLOCATIONPROVIDER 83 84 GCLocProviderPriv(); 85 86 private: 87 enum class Accuracy { Unset, Low, High }; 88 // States: 89 // Uninit: The default / initial state, with no client proxy yet. 90 // Initing: Takes care of establishing the client connection (GetClient / 91 // ConnectClient / SetDesktopID). 92 // SettingAccuracy: Does SetAccuracy operation, knows it should just go idle 93 // after finishing it. 94 // SettingAccuracyForStart: Does SetAccuracy operation, knows it then needs 95 // to do a Start operation after finishing it. 96 // Idle: Fully initialized, but not running state (quiescent). 97 // Starting: Starts the client by calling the Start D-Bus method. 98 // Started: Normal running state. 99 // Stopping: Stops the client by calling the Stop D-Bus method, knows it 100 // should just go idle after finishing it. 101 // StoppingForRestart: Stops the client by calling the Stop D-Bus method as 102 // a part of a Stop -> Start sequence (with possibly 103 // an accuracy update between these method calls). 104 // 105 // Valid state transitions are: 106 // (any state) -> Uninit: Transition when a D-Bus call failed or 107 // provided invalid data. 108 // 109 // Watch() startup path: 110 // Uninit -> Initing: Transition after getting the very first Watch() 111 // request 112 // or any such request while not having the client proxy. 113 // Initing -> SettingAccuracyForStart: Transition after getting a successful 114 // SetDesktopID response. 115 // SettingAccuracyForStart -> Starting: Transition after getting a 116 // successful 117 // SetAccuracy response. 118 // Idle -> Starting: Transition after getting a Watch() request while in 119 // fully 120 // initialized, but not running state. 121 // SettingAccuracy -> SettingAccuracyForStart: Transition after getting a 122 // Watch() 123 // request in the middle of 124 // setting accuracy during idle 125 // status. 126 // Stopping -> StoppingForRestart: Transition after getting a Watch() 127 // request 128 // in the middle of doing a Stop D-Bus call 129 // for idle status. 130 // StoppingForRestart -> Starting: Transition after getting a successful 131 // Stop response as a part of a Stop -> 132 // Start sequence while the previously set 133 // accuracy is still correct. 134 // StoppingForRestart -> SettingAccuracyForStart: Transition after getting 135 // a successful Stop response 136 // as a part of a Stop -> 137 // Start sequence but the set 138 // accuracy needs updating. 139 // Starting -> Started: Transition after getting a successful Start 140 // response. 141 // 142 // Shutdown() path: 143 // (any state) -> Uninit: Transition when not reusing the client proxy for 144 // any reason. 145 // Started -> Stopping: Transition from normal running state when reusing 146 // the client proxy. 147 // SettingAccuracyForStart -> SettingAccuracy: Transition when doing 148 // a shutdown in the middle of 149 // setting accuracy for a start 150 // when reusing the client 151 // proxy. 152 // SettingAccuracy -> Idle: Transition after getting a successful 153 // SetAccuracy 154 // response. 155 // StoppingForRestart -> Stopping: Transition when doing shutdown 156 // in the middle of a Stop -> Start sequence 157 // when reusing the client proxy. 158 // Stopping -> Idle: Transition after getting a successful Stop response. 159 // 160 // SetHighAccuracy() path: 161 // Started -> StoppingForRestart: Transition when accuracy needs updating 162 // on a running client. 163 // (the rest of the flow in StoppingForRestart state is the same as when 164 // being in this state in the Watch() startup path) 165 enum class ClientState { 166 Uninit, 167 Initing, 168 SettingAccuracy, 169 SettingAccuracyForStart, 170 Idle, 171 Starting, 172 Started, 173 Stopping, 174 StoppingForRestart 175 }; 176 177 ~GCLocProviderPriv(); 178 179 static bool AlwaysHighAccuracy(); 180 181 void SetState(ClientState aNewState, const char* aNewStateStr); 182 183 void Update(nsIDOMGeoPosition* aPosition); 184 MOZ_CAN_RUN_SCRIPT void NotifyError(int aError); 185 MOZ_CAN_RUN_SCRIPT void DBusProxyError(const GError* aGError, 186 bool aResetManager = false); 187 188 MOZ_CAN_RUN_SCRIPT static void GetClientResponse(GDBusProxy* aProxy, 189 GAsyncResult* aResult, 190 gpointer aUserData); 191 void ConnectClient(const gchar* aClientPath); 192 MOZ_CAN_RUN_SCRIPT static void ConnectClientResponse(GObject* aObject, 193 GAsyncResult* aResult, 194 gpointer aUserData); 195 void SetDesktopID(); 196 MOZ_CAN_RUN_SCRIPT static void SetDesktopIDResponse(GDBusProxy* aProxy, 197 GAsyncResult* aResult, 198 gpointer aUserData); 199 void SetAccuracy(); 200 MOZ_CAN_RUN_SCRIPT static void SetAccuracyResponse(GDBusProxy* aProxy, 201 GAsyncResult* aResult, 202 gpointer aUserData); 203 void StartClient(); 204 MOZ_CAN_RUN_SCRIPT static void StartClientResponse(GDBusProxy* aProxy, 205 GAsyncResult* aResult, 206 gpointer aUserData); 207 void StopClient(bool aForRestart); 208 MOZ_CAN_RUN_SCRIPT static void StopClientResponse(GDBusProxy* aProxy, 209 GAsyncResult* aResult, 210 gpointer aUserData); 211 void StopClientNoWait(); 212 void MaybeRestartForAccuracy(); 213 214 MOZ_CAN_RUN_SCRIPT static void GCManagerOwnerNotify(GObject* aObject, 215 GParamSpec* aPSpec, 216 gpointer aUserData); 217 static void GCClientSignal(GDBusProxy* aProxy, gchar* aSenderName, 218 gchar* aSignalName, GVariant* aParameters, 219 gpointer aUserData); 220 void ConnectLocation(const gchar* aLocationPath); 221 static bool GetLocationProperty(GDBusProxy* aProxyLocation, 222 const gchar* aName, double* aOut); 223 static void ConnectLocationResponse(GObject* aObject, GAsyncResult* aResult, 224 gpointer aUserData); 225 226 void StartLastPositionTimer(); 227 void StopPositionTimer(); 228 void UpdateLastPosition(); 229 230 void StartMLSFallbackTimerIfNeeded(); 231 void StopMLSFallbackTimer(); 232 void MLSFallbackTimerFired(); 233 234 bool InDBusCall(); 235 bool InDBusStoppingCall(); 236 bool InDBusStoppedCall(); 237 238 void DeleteManager(); 239 void DoShutdown(bool aDeleteClient, bool aDeleteManager); 240 void DoShutdownClearCallback(bool aDestroying); 241 242 nsresult FallbackToMLS(MLSFallback::FallbackReason aReason); 243 void StopMLSFallback(); 244 245 void WatchStart(); 246 247 Accuracy mAccuracyWanted = Accuracy::Unset; 248 Accuracy mAccuracySet = Accuracy::Unset; 249 RefPtr<GDBusProxy> mProxyManager; 250 RefPtr<GDBusProxy> mProxyClient; 251 RefPtr<GCancellable> mCancellable; 252 nsCOMPtr<nsIGeolocationUpdate> mCallback; 253 ClientState mClientState = ClientState::Uninit; 254 RefPtr<nsIDOMGeoPosition> mLastPosition; 255 RefPtr<nsITimer> mLastPositionTimer; 256 RefPtr<nsITimer> mMLSFallbackTimer; 257 RefPtr<MLSFallback> mMLSFallback; 258 }; 259 260 class GCLocWeakCallback final : public nsITimerCallback, public nsINamed { 261 using Method = void (GCLocProviderPriv::*)(); 262 263 public: 264 NS_DECL_ISUPPORTS 265 NS_DECL_NSITIMERCALLBACK 266 267 explicit GCLocWeakCallback(GCLocProviderPriv* aParent, const char* aName, 268 Method aMethod) 269 : mParent(aParent), mName(aName), mMethod(aMethod) {} 270 271 NS_IMETHOD GetName(nsACString& aName) override { 272 aName = mName; 273 return NS_OK; 274 } 275 276 private: 277 ~GCLocWeakCallback() = default; 278 WeakPtr<GCLocProviderPriv> mParent; 279 const char* mName = nullptr; 280 Method mMethod = nullptr; 281 }; 282 283 NS_IMPL_ISUPPORTS(GCLocWeakCallback, nsITimerCallback, nsINamed) 284 285 NS_IMETHODIMP 286 GCLocWeakCallback::Notify(nsITimer* aTimer) { 287 if (RefPtr parent = mParent.get()) { 288 (parent->*mMethod)(); 289 } 290 return NS_OK; 291 } 292 293 // 294 // GCLocProviderPriv 295 // 296 297 #define GCLP_SETSTATE(this, state) this->SetState(ClientState::state, #state) 298 299 GCLocProviderPriv::GCLocProviderPriv() { 300 if (AlwaysHighAccuracy()) { 301 mAccuracyWanted = Accuracy::High; 302 } else { 303 mAccuracyWanted = Accuracy::Low; 304 } 305 } 306 307 GCLocProviderPriv::~GCLocProviderPriv() { DoShutdownClearCallback(true); } 308 309 bool GCLocProviderPriv::AlwaysHighAccuracy() { 310 return StaticPrefs::geo_provider_geoclue_always_high_accuracy(); 311 } 312 313 void GCLocProviderPriv::SetState(ClientState aNewState, 314 const char* aNewStateStr) { 315 if (mClientState == aNewState) { 316 return; 317 } 318 319 GCL_LOG(Debug, "changing state to %s", aNewStateStr); 320 mClientState = aNewState; 321 } 322 323 void GCLocProviderPriv::Update(nsIDOMGeoPosition* aPosition) { 324 if (!mCallback) { 325 return; 326 } 327 328 mCallback->Update(aPosition); 329 } 330 331 void GCLocProviderPriv::UpdateLastPosition() { 332 MOZ_DIAGNOSTIC_ASSERT(mLastPosition, "No last position to update"); 333 if (mMLSFallbackTimer) { 334 // We are not going to wait for MLS fallback anymore 335 glean::geolocation::fallback 336 .EnumGet(glean::geolocation::FallbackLabel::eNone) 337 .Add(); 338 } 339 StopPositionTimer(); 340 StopMLSFallbackTimer(); 341 Update(mLastPosition); 342 } 343 344 nsresult GCLocProviderPriv::FallbackToMLS(MLSFallback::FallbackReason aReason) { 345 GCL_LOG(Debug, "trying to fall back to MLS"); 346 StopMLSFallback(); 347 348 RefPtr fallback = new MLSFallback(0); 349 MOZ_TRY(fallback->Startup(mCallback, aReason)); 350 351 GCL_LOG(Debug, "Started up MLS fallback"); 352 mMLSFallback = std::move(fallback); 353 return NS_OK; 354 } 355 356 void GCLocProviderPriv::StopMLSFallback() { 357 if (!mMLSFallback) { 358 return; 359 } 360 GCL_LOG(Debug, "Clearing MLS fallback"); 361 if (mMLSFallback) { 362 mMLSFallback->Shutdown(MLSFallback::ShutdownReason::ProviderShutdown); 363 mMLSFallback = nullptr; 364 } 365 } 366 367 void GCLocProviderPriv::NotifyError(int aError) { 368 if (!mCallback) { 369 return; 370 } 371 372 // We errored out, try to fall back to MLS. 373 if (NS_SUCCEEDED(FallbackToMLS(MLSFallback::FallbackReason::Error))) { 374 return; 375 } 376 377 nsCOMPtr callback = mCallback; 378 callback->NotifyError(aError); 379 } 380 381 void GCLocProviderPriv::DBusProxyError(const GError* aGError, 382 bool aResetManager) { 383 // that G_DBUS_ERROR below is actually a function call, not a constant 384 GQuark gdbusDomain = G_DBUS_ERROR; 385 int error = GeolocationPositionError_Binding::POSITION_UNAVAILABLE; 386 if (aGError) { 387 if (g_error_matches(aGError, gdbusDomain, G_DBUS_ERROR_TIMEOUT) || 388 g_error_matches(aGError, gdbusDomain, G_DBUS_ERROR_TIMED_OUT)) { 389 error = GeolocationPositionError_Binding::TIMEOUT; 390 } else if (g_error_matches(aGError, gdbusDomain, 391 G_DBUS_ERROR_LIMITS_EXCEEDED) || 392 g_error_matches(aGError, gdbusDomain, 393 G_DBUS_ERROR_ACCESS_DENIED) || 394 g_error_matches(aGError, gdbusDomain, 395 G_DBUS_ERROR_AUTH_FAILED)) { 396 error = GeolocationPositionError_Binding::PERMISSION_DENIED; 397 } 398 } 399 400 DoShutdown(true, aResetManager); 401 NotifyError(error); 402 } 403 404 void GCLocProviderPriv::GetClientResponse(GDBusProxy* aProxy, 405 GAsyncResult* aResult, 406 gpointer aUserData) { 407 GUniquePtr<GError> error; 408 RefPtr<GVariant> variant = dont_AddRef( 409 g_dbus_proxy_call_finish(aProxy, aResult, getter_Transfers(error))); 410 if (!variant) { 411 GCL_LOG(Error, "Failed to get client: %s\n", error->message); 412 // if cancelled |self| might no longer be there 413 if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) { 414 RefPtr self = static_cast<GCLocProviderPriv*>(aUserData); 415 self->DBusProxyError(error.get(), true); 416 } 417 return; 418 } 419 420 RefPtr self = static_cast<GCLocProviderPriv*>(aUserData); 421 MOZ_DIAGNOSTIC_ASSERT(self->mClientState == ClientState::Initing, 422 "Client in a wrong state"); 423 424 auto signalError = MakeScopeExit([&]() MOZ_CAN_RUN_SCRIPT_BOUNDARY { 425 self->DBusProxyError(nullptr, true); 426 }); 427 428 if (!g_variant_is_of_type(variant, G_VARIANT_TYPE_TUPLE)) { 429 GCL_LOG(Error, "Unexpected get client call return type: %s\n", 430 g_variant_get_type_string(variant)); 431 return; 432 } 433 434 if (g_variant_n_children(variant) < 1) { 435 GCL_LOG(Error, 436 "Not enough params in get client call return: %" G_GSIZE_FORMAT 437 "\n", 438 g_variant_n_children(variant)); 439 return; 440 } 441 442 variant = dont_AddRef(g_variant_get_child_value(variant, 0)); 443 if (!g_variant_is_of_type(variant, G_VARIANT_TYPE_OBJECT_PATH)) { 444 GCL_LOG(Error, "Unexpected get client call return type inside tuple: %s\n", 445 g_variant_get_type_string(variant)); 446 return; 447 } 448 449 const gchar* clientPath = g_variant_get_string(variant, nullptr); 450 GCL_LOG(Debug, "Client path: %s\n", clientPath); 451 452 signalError.release(); 453 self->ConnectClient(clientPath); 454 } 455 456 void GCLocProviderPriv::ConnectClient(const gchar* aClientPath) { 457 MOZ_DIAGNOSTIC_ASSERT(mClientState == ClientState::Initing, 458 "Client in a wrong state"); 459 MOZ_ASSERT(mCancellable, "Watch() wasn't successfully called"); 460 nsAppShell::DBusConnectionCheck(); 461 g_dbus_proxy_new_for_bus( 462 G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr, kGeoclueBusName, 463 aClientPath, kGCClientInterface, mCancellable, 464 reinterpret_cast<GAsyncReadyCallback>(ConnectClientResponse), this); 465 } 466 467 void GCLocProviderPriv::ConnectClientResponse(GObject* aObject, 468 GAsyncResult* aResult, 469 gpointer aUserData) { 470 nsAppShell::DBusConnectionCheck(); 471 GUniquePtr<GError> error; 472 RefPtr<GDBusProxy> proxyClient = 473 dont_AddRef(g_dbus_proxy_new_finish(aResult, getter_Transfers(error))); 474 if (!proxyClient) { 475 // if cancelled |self| might no longer be there 476 if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) { 477 GCL_LOG(Error, "Failed to connect to client: %s\n", error->message); 478 RefPtr self = static_cast<GCLocProviderPriv*>(aUserData); 479 self->DBusProxyError(error.get()); 480 } 481 return; 482 } 483 RefPtr self = static_cast<GCLocProviderPriv*>(aUserData); 484 self->mProxyClient = std::move(proxyClient); 485 486 MOZ_DIAGNOSTIC_ASSERT(self->mClientState == ClientState::Initing, 487 "Client in a wrong state"); 488 489 GCL_LOG(Info, "Client interface connected\n"); 490 491 g_signal_connect(self->mProxyClient, "g-signal", G_CALLBACK(GCClientSignal), 492 self); 493 self->SetDesktopID(); 494 } 495 496 void GCLocProviderPriv::SetDesktopID() { 497 MOZ_DIAGNOSTIC_ASSERT(mClientState == ClientState::Initing, 498 "Client in a wrong state"); 499 MOZ_DIAGNOSTIC_ASSERT(mProxyClient && mCancellable, 500 "Watch() wasn't successfully called"); 501 502 nsAutoCString appName; 503 gAppData->GetDBusAppName(appName); 504 nsAppShell::DBusConnectionCheck(); 505 g_dbus_proxy_call(mProxyClient, kDBPropertySetMethod, 506 g_variant_new("(ssv)", kGCClientInterface, "DesktopId", 507 g_variant_new_string(appName.get())), 508 G_DBUS_CALL_FLAGS_NONE, -1, mCancellable, 509 reinterpret_cast<GAsyncReadyCallback>(SetDesktopIDResponse), 510 this); 511 } 512 513 void GCLocProviderPriv::SetDesktopIDResponse(GDBusProxy* aProxy, 514 GAsyncResult* aResult, 515 gpointer aUserData) { 516 GUniquePtr<GError> error; 517 nsAppShell::DBusConnectionCheck(); 518 RefPtr<GVariant> variant = dont_AddRef( 519 g_dbus_proxy_call_finish(aProxy, aResult, getter_Transfers(error))); 520 if (!variant) { 521 // if cancelled |self| might no longer be there 522 if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) { 523 GCL_LOG(Error, "Failed to set DesktopId: %s\n", error->message); 524 RefPtr self = static_cast<GCLocProviderPriv*>(aUserData); 525 self->DBusProxyError(error.get()); 526 } 527 return; 528 } 529 530 RefPtr self = static_cast<GCLocProviderPriv*>(aUserData); 531 MOZ_DIAGNOSTIC_ASSERT(self->mClientState == ClientState::Initing, 532 "Client in a wrong state"); 533 534 GCLP_SETSTATE(self, Idle); 535 self->SetAccuracy(); 536 } 537 538 void GCLocProviderPriv::SetAccuracy() { 539 MOZ_DIAGNOSTIC_ASSERT(mClientState == ClientState::Idle, 540 "Client in a wrong state"); 541 MOZ_DIAGNOSTIC_ASSERT(mProxyClient && mCancellable, 542 "Watch() wasn't successfully called"); 543 MOZ_ASSERT(mAccuracyWanted != Accuracy::Unset, "Invalid accuracy"); 544 545 guint32 accuracy; 546 if (mAccuracyWanted == Accuracy::High) { 547 accuracy = (guint32)GCAccuracyLevel::Exact; 548 } else { 549 accuracy = (guint32)GCAccuracyLevel::City; 550 } 551 552 mAccuracySet = mAccuracyWanted; 553 GCLP_SETSTATE(this, SettingAccuracyForStart); 554 nsAppShell::DBusConnectionCheck(); 555 g_dbus_proxy_call( 556 mProxyClient, kDBPropertySetMethod, 557 g_variant_new("(ssv)", kGCClientInterface, "RequestedAccuracyLevel", 558 g_variant_new_uint32(accuracy)), 559 G_DBUS_CALL_FLAGS_NONE, -1, mCancellable, 560 reinterpret_cast<GAsyncReadyCallback>(SetAccuracyResponse), this); 561 } 562 563 void GCLocProviderPriv::SetAccuracyResponse(GDBusProxy* aProxy, 564 GAsyncResult* aResult, 565 gpointer aUserData) { 566 nsAppShell::DBusConnectionCheck(); 567 GUniquePtr<GError> error; 568 RefPtr<GVariant> variant = dont_AddRef( 569 g_dbus_proxy_call_finish(aProxy, aResult, getter_Transfers(error))); 570 if (!variant) { 571 // if cancelled |self| might no longer be there 572 if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) { 573 GCL_LOG(Error, "Failed to set requested accuracy level: %s\n", 574 error->message); 575 RefPtr self = static_cast<GCLocProviderPriv*>(aUserData); 576 self->DBusProxyError(error.get()); 577 } 578 return; 579 } 580 581 RefPtr self = static_cast<GCLocProviderPriv*>(aUserData); 582 MOZ_DIAGNOSTIC_ASSERT( 583 self->mClientState == ClientState::SettingAccuracyForStart || 584 self->mClientState == ClientState::SettingAccuracy, 585 "Client in a wrong state"); 586 bool wantStart = self->mClientState == ClientState::SettingAccuracyForStart; 587 GCLP_SETSTATE(self, Idle); 588 589 if (wantStart) { 590 self->StartClient(); 591 } 592 } 593 594 void GCLocProviderPriv::StartClient() { 595 MOZ_DIAGNOSTIC_ASSERT(mClientState == ClientState::Idle, 596 "Client in a wrong state"); 597 MOZ_DIAGNOSTIC_ASSERT(mProxyClient && mCancellable, 598 "Watch() wasn't successfully called"); 599 GCLP_SETSTATE(this, Starting); 600 nsAppShell::DBusConnectionCheck(); 601 g_dbus_proxy_call( 602 mProxyClient, "Start", nullptr, G_DBUS_CALL_FLAGS_NONE, -1, mCancellable, 603 reinterpret_cast<GAsyncReadyCallback>(StartClientResponse), this); 604 } 605 606 void GCLocProviderPriv::StartClientResponse(GDBusProxy* aProxy, 607 GAsyncResult* aResult, 608 gpointer aUserData) { 609 GUniquePtr<GError> error; 610 nsAppShell::DBusConnectionCheck(); 611 RefPtr<GVariant> variant = dont_AddRef( 612 g_dbus_proxy_call_finish(aProxy, aResult, getter_Transfers(error))); 613 if (!variant) { 614 // if cancelled |self| might no longer be there 615 if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) { 616 GCL_LOG(Error, "Failed to start client: %s\n", error->message); 617 RefPtr self = static_cast<GCLocProviderPriv*>(aUserData); 618 /* 619 * A workaround for 620 * https://gitlab.freedesktop.org/geoclue/geoclue/-/issues/143 We need to 621 * get a new client instance once the agent finally connects to the 622 * Geoclue service, otherwise every Start request on the old client 623 * interface will be denied. We need to reconnect to the Manager interface 624 * to achieve this since otherwise GetClient call will simply return the 625 * old client instance. 626 */ 627 bool resetManager = g_error_matches(error.get(), G_DBUS_ERROR, 628 G_DBUS_ERROR_ACCESS_DENIED); 629 self->DBusProxyError(error.get(), resetManager); 630 } 631 return; 632 } 633 634 RefPtr self = static_cast<GCLocProviderPriv*>(aUserData); 635 MOZ_DIAGNOSTIC_ASSERT(self->mClientState == ClientState::Starting, 636 "Client in a wrong state"); 637 GCLP_SETSTATE(self, Started); 638 // If we're started, and we don't get any location update in a reasonable 639 // amount of time, we fallback to MLS. 640 self->StartMLSFallbackTimerIfNeeded(); 641 self->MaybeRestartForAccuracy(); 642 } 643 644 void GCLocProviderPriv::StopClient(bool aForRestart) { 645 MOZ_DIAGNOSTIC_ASSERT(mClientState == ClientState::Started, 646 "Client in a wrong state"); 647 MOZ_DIAGNOSTIC_ASSERT(mProxyClient && mCancellable, 648 "Watch() wasn't successfully called"); 649 650 if (aForRestart) { 651 GCLP_SETSTATE(this, StoppingForRestart); 652 } else { 653 GCLP_SETSTATE(this, Stopping); 654 } 655 656 nsAppShell::DBusConnectionCheck(); 657 g_dbus_proxy_call( 658 mProxyClient, "Stop", nullptr, G_DBUS_CALL_FLAGS_NONE, -1, mCancellable, 659 reinterpret_cast<GAsyncReadyCallback>(StopClientResponse), this); 660 } 661 662 void GCLocProviderPriv::StopClientResponse(GDBusProxy* aProxy, 663 GAsyncResult* aResult, 664 gpointer aUserData) { 665 nsAppShell::DBusConnectionCheck(); 666 GUniquePtr<GError> error; 667 RefPtr<GVariant> variant = dont_AddRef( 668 g_dbus_proxy_call_finish(aProxy, aResult, getter_Transfers(error))); 669 if (!variant) { 670 // if cancelled |self| might no longer be there 671 if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) { 672 GCL_LOG(Error, "Failed to stop client: %s\n", error->message); 673 RefPtr self = static_cast<GCLocProviderPriv*>(aUserData); 674 self->DBusProxyError(error.get()); 675 } 676 return; 677 } 678 679 RefPtr self = static_cast<GCLocProviderPriv*>(aUserData); 680 MOZ_DIAGNOSTIC_ASSERT(self->InDBusStoppingCall(), "Client in a wrong state"); 681 bool wantRestart = self->mClientState == ClientState::StoppingForRestart; 682 GCLP_SETSTATE(self, Idle); 683 684 if (!wantRestart) { 685 return; 686 } 687 688 if (self->mAccuracyWanted != self->mAccuracySet) { 689 self->SetAccuracy(); 690 } else { 691 self->StartClient(); 692 } 693 } 694 695 void GCLocProviderPriv::StopClientNoWait() { 696 MOZ_DIAGNOSTIC_ASSERT(mProxyClient, "Watch() wasn't successfully called"); 697 nsAppShell::DBusConnectionCheck(); 698 g_dbus_proxy_call(mProxyClient, "Stop", nullptr, G_DBUS_CALL_FLAGS_NONE, -1, 699 nullptr, nullptr, nullptr); 700 } 701 702 void GCLocProviderPriv::MaybeRestartForAccuracy() { 703 if (mAccuracyWanted == mAccuracySet) { 704 return; 705 } 706 707 if (mClientState != ClientState::Started) { 708 return; 709 } 710 711 // Setting a new accuracy requires restarting the client 712 StopClient(true); 713 } 714 715 void GCLocProviderPriv::GCManagerOwnerNotify(GObject* aObject, 716 GParamSpec* aPSpec, 717 gpointer aUserData) { 718 RefPtr self = static_cast<GCLocProviderPriv*>(aUserData); 719 GUniquePtr<gchar> managerOwner( 720 g_dbus_proxy_get_name_owner(self->mProxyManager)); 721 if (!managerOwner) { 722 GCL_LOG(Info, "The Manager interface has lost its owner\n"); 723 self->DBusProxyError(nullptr, true); 724 } 725 } 726 727 void GCLocProviderPriv::GCClientSignal(GDBusProxy* aProxy, gchar* aSenderName, 728 gchar* aSignalName, 729 GVariant* aParameters, 730 gpointer aUserData) { 731 GCL_LOG(Info, "%s: %s (%s)\n", __PRETTY_FUNCTION__, aSignalName, 732 GUniquePtr<gchar>(g_variant_print(aParameters, TRUE)).get()); 733 734 if (g_strcmp0(aSignalName, "LocationUpdated")) { 735 return; 736 } 737 738 if (!g_variant_is_of_type(aParameters, G_VARIANT_TYPE_TUPLE)) { 739 GCL_LOG(Error, "Unexpected location updated signal params type: %s\n", 740 g_variant_get_type_string(aParameters)); 741 return; 742 } 743 744 if (g_variant_n_children(aParameters) < 2) { 745 GCL_LOG(Error, 746 "Not enough params in location updated signal: %" G_GSIZE_FORMAT 747 "\n", 748 g_variant_n_children(aParameters)); 749 return; 750 } 751 752 RefPtr<GVariant> variant = 753 dont_AddRef(g_variant_get_child_value(aParameters, 1)); 754 if (!g_variant_is_of_type(variant, G_VARIANT_TYPE_OBJECT_PATH)) { 755 GCL_LOG(Error, 756 "Unexpected location updated signal new location path type: %s\n", 757 g_variant_get_type_string(variant)); 758 return; 759 } 760 761 RefPtr self = static_cast<GCLocProviderPriv*>(aUserData); 762 const gchar* locationPath = g_variant_get_string(variant, nullptr); 763 GCL_LOG(Verbose, "New location path: %s\n", locationPath); 764 self->ConnectLocation(locationPath); 765 } 766 767 void GCLocProviderPriv::ConnectLocation(const gchar* aLocationPath) { 768 MOZ_ASSERT(mCancellable, "Startup() wasn't successfully called"); 769 nsAppShell::DBusConnectionCheck(); 770 g_dbus_proxy_new_for_bus( 771 G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr, kGeoclueBusName, 772 aLocationPath, kGCLocationInterface, mCancellable, 773 reinterpret_cast<GAsyncReadyCallback>(ConnectLocationResponse), this); 774 } 775 776 bool GCLocProviderPriv::GetLocationProperty(GDBusProxy* aProxyLocation, 777 const gchar* aName, double* aOut) { 778 RefPtr<GVariant> property = 779 dont_AddRef(g_dbus_proxy_get_cached_property(aProxyLocation, aName)); 780 if (!g_variant_is_of_type(property, G_VARIANT_TYPE_DOUBLE)) { 781 GCL_LOG(Error, "Unexpected location property %s type: %s\n", aName, 782 g_variant_get_type_string(property)); 783 return false; 784 } 785 786 *aOut = g_variant_get_double(property); 787 return true; 788 } 789 790 void GCLocProviderPriv::ConnectLocationResponse(GObject* aObject, 791 GAsyncResult* aResult, 792 gpointer aUserData) { 793 GUniquePtr<GError> error; 794 RefPtr<GDBusProxy> proxyLocation = 795 dont_AddRef(g_dbus_proxy_new_finish(aResult, getter_Transfers(error))); 796 if (!proxyLocation) { 797 if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) { 798 GCL_LOG(Warning, "Failed to connect to location: %s\n", error->message); 799 } 800 return; 801 } 802 803 RefPtr self = static_cast<GCLocProviderPriv*>(aUserData); 804 /* 805 * nsGeoPositionCoords will convert NaNs to null for optional properties of 806 * the JavaScript Coordinates object. 807 */ 808 double lat = UnspecifiedNaN<double>(); 809 double lon = UnspecifiedNaN<double>(); 810 double alt = UnspecifiedNaN<double>(); 811 double hError = UnspecifiedNaN<double>(); 812 const double vError = UnspecifiedNaN<double>(); 813 double heading = UnspecifiedNaN<double>(); 814 double speed = UnspecifiedNaN<double>(); 815 struct { 816 const gchar* name; 817 double* out; 818 } props[] = { 819 {"Latitude", &lat}, {"Longitude", &lon}, {"Altitude", &alt}, 820 {"Accuracy", &hError}, {"Heading", &heading}, {"Speed", &speed}, 821 }; 822 823 for (auto& prop : props) { 824 if (!GetLocationProperty(proxyLocation, prop.name, prop.out)) { 825 return; 826 } 827 } 828 829 if (alt < kGCMinAlt) { 830 alt = UnspecifiedNaN<double>(); 831 } 832 if (speed < 0) { 833 speed = UnspecifiedNaN<double>(); 834 } 835 if (heading < 0 || std::isnan(speed) || speed == 0) { 836 heading = UnspecifiedNaN<double>(); 837 } 838 839 GCL_LOG(Info, "New location: %f %f +-%fm @ %gm; hdg %f spd %fm/s\n", lat, lon, 840 hError, alt, heading, speed); 841 842 self->mLastPosition = 843 new nsGeoPosition(lat, lon, alt, hError, vError, heading, speed, 844 PR_Now() / PR_USEC_PER_MSEC); 845 self->UpdateLastPosition(); 846 } 847 848 void GCLocProviderPriv::StartLastPositionTimer() { 849 MOZ_DIAGNOSTIC_ASSERT(mLastPosition, "no last position to report"); 850 851 StopPositionTimer(); 852 853 RefPtr timerCallback = new GCLocWeakCallback( 854 this, "UpdateLastPosition", &GCLocProviderPriv::UpdateLastPosition); 855 NS_NewTimerWithCallback(getter_AddRefs(mLastPositionTimer), timerCallback, 856 1000, nsITimer::TYPE_ONE_SHOT); 857 } 858 859 void GCLocProviderPriv::StopPositionTimer() { 860 if (!mLastPositionTimer) { 861 return; 862 } 863 864 mLastPositionTimer->Cancel(); 865 mLastPositionTimer = nullptr; 866 } 867 868 void GCLocProviderPriv::StartMLSFallbackTimerIfNeeded() { 869 StopMLSFallbackTimer(); 870 if (mLastPosition) { 871 // If we already have a location we're good. 872 return; 873 } 874 875 uint32_t delay = StaticPrefs::geo_provider_geoclue_mls_fallback_timeout_ms(); 876 if (!delay) { 877 return; 878 } 879 880 RefPtr timerCallback = new GCLocWeakCallback( 881 this, "MLSFallbackTimerFired", &GCLocProviderPriv::MLSFallbackTimerFired); 882 NS_NewTimerWithCallback(getter_AddRefs(mMLSFallbackTimer), timerCallback, 883 delay, nsITimer::TYPE_ONE_SHOT); 884 } 885 886 void GCLocProviderPriv::StopMLSFallbackTimer() { 887 if (!mMLSFallbackTimer) { 888 return; 889 } 890 mMLSFallbackTimer->Cancel(); 891 mMLSFallbackTimer = nullptr; 892 } 893 894 void GCLocProviderPriv::MLSFallbackTimerFired() { 895 mMLSFallbackTimer = nullptr; 896 897 if (mMLSFallback || mLastPosition || mClientState != ClientState::Started) { 898 return; 899 } 900 901 GCL_LOG(Info, 902 "Didn't get a location in a reasonable amount of time, trying to " 903 "fall back to MLS"); 904 FallbackToMLS(MLSFallback::FallbackReason::Timeout); 905 } 906 907 // Did we made some D-Bus call and are still waiting for its response? 908 bool GCLocProviderPriv::InDBusCall() { 909 return mClientState == ClientState::Initing || 910 mClientState == ClientState::SettingAccuracy || 911 mClientState == ClientState::SettingAccuracyForStart || 912 mClientState == ClientState::Starting || 913 mClientState == ClientState::Stopping || 914 mClientState == ClientState::StoppingForRestart; 915 } 916 917 bool GCLocProviderPriv::InDBusStoppingCall() { 918 return mClientState == ClientState::Stopping || 919 mClientState == ClientState::StoppingForRestart; 920 } 921 922 /* 923 * Did we made some D-Bus call while stopped and 924 * are still waiting for its response? 925 */ 926 bool GCLocProviderPriv::InDBusStoppedCall() { 927 return mClientState == ClientState::SettingAccuracy || 928 mClientState == ClientState::SettingAccuracyForStart; 929 } 930 931 void GCLocProviderPriv::DeleteManager() { 932 if (!mProxyManager) { 933 return; 934 } 935 936 g_signal_handlers_disconnect_matched(mProxyManager, G_SIGNAL_MATCH_DATA, 0, 0, 937 nullptr, nullptr, this); 938 mProxyManager = nullptr; 939 } 940 941 void GCLocProviderPriv::DoShutdown(bool aDeleteClient, bool aDeleteManager) { 942 MOZ_DIAGNOSTIC_ASSERT( 943 !aDeleteManager || aDeleteClient, 944 "deleting manager proxy requires deleting client one, too"); 945 nsAppShell::DBusConnectionCheck(); 946 947 // Invalidate the cached last position 948 StopPositionTimer(); 949 StopMLSFallbackTimer(); 950 mLastPosition = nullptr; 951 952 /* 953 * Do we need to delete the D-Bus proxy (or proxies)? 954 * Either because that's what our caller wanted, or because we are set to 955 * never reuse them, or because we are in a middle of some D-Bus call while 956 * having the service running (and so not being able to issue an immediate 957 * Stop call). 958 */ 959 if (aDeleteClient || !kGCReuseDBusProxy || 960 (InDBusCall() && !InDBusStoppingCall() && !InDBusStoppedCall())) { 961 if (mClientState == ClientState::Started) { 962 StopClientNoWait(); 963 GCLP_SETSTATE(this, Idle); 964 } 965 if (mProxyClient) { 966 g_signal_handlers_disconnect_matched(mProxyClient, G_SIGNAL_MATCH_DATA, 0, 967 0, nullptr, nullptr, this); 968 } 969 if (mCancellable) { 970 g_cancellable_cancel(mCancellable); 971 mCancellable = nullptr; 972 } 973 mProxyClient = nullptr; 974 975 if (aDeleteManager || !kGCReuseDBusProxy) { 976 DeleteManager(); 977 } 978 979 GCLP_SETSTATE(this, Uninit); 980 } else if (mClientState == ClientState::Started) { 981 StopClient(false); 982 } else if (mClientState == ClientState::SettingAccuracyForStart) { 983 GCLP_SETSTATE(this, SettingAccuracy); 984 } else if (mClientState == ClientState::StoppingForRestart) { 985 GCLP_SETSTATE(this, Stopping); 986 } 987 } 988 989 void GCLocProviderPriv::DoShutdownClearCallback(bool aDestroying) { 990 mCallback = nullptr; 991 StopMLSFallback(); 992 DoShutdown(aDestroying, aDestroying); 993 } 994 995 NS_IMPL_ISUPPORTS(GCLocProviderPriv, nsIGeolocationProvider) 996 997 // nsIGeolocationProvider 998 // 999 1000 /* 1001 * The Startup() method should only succeed if Geoclue is available on D-Bus 1002 * so it can be used for determining whether to continue with this geolocation 1003 * provider in Geolocation.cpp 1004 */ 1005 NS_IMETHODIMP 1006 GCLocProviderPriv::Startup() { 1007 if (mProxyManager) { 1008 return NS_OK; 1009 } 1010 1011 MOZ_DIAGNOSTIC_ASSERT(mClientState == ClientState::Uninit, 1012 "Client in a initialized state but no manager"); 1013 1014 GUniquePtr<GError> error; 1015 nsAppShell::DBusConnectionCheck(); 1016 mProxyManager = dont_AddRef(g_dbus_proxy_new_for_bus_sync( 1017 G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr, kGeoclueBusName, 1018 kGCManagerPath, kGCManagerInterface, nullptr, getter_Transfers(error))); 1019 if (!mProxyManager) { 1020 GCL_LOG(Info, "Cannot connect to the Manager interface: %s\n", 1021 error->message); 1022 return NS_ERROR_FAILURE; 1023 } 1024 1025 g_signal_connect(mProxyManager, "notify::g-name-owner", 1026 G_CALLBACK(GCManagerOwnerNotify), this); 1027 1028 GUniquePtr<gchar> managerOwner(g_dbus_proxy_get_name_owner(mProxyManager)); 1029 if (!managerOwner) { 1030 GCL_LOG(Info, "The Manager interface has no owner\n"); 1031 DeleteManager(); 1032 return NS_ERROR_FAILURE; 1033 } 1034 1035 GCL_LOG(Info, "Manager interface connected successfully\n"); 1036 1037 return NS_OK; 1038 } 1039 1040 void GCLocProviderPriv::WatchStart() { 1041 if (mClientState == ClientState::Idle) { 1042 StartClient(); 1043 } else if (mClientState == ClientState::Started) { 1044 if (mLastPosition && !mLastPositionTimer) { 1045 GCL_LOG(Verbose, 1046 "Will report the existing position if new one doesn't come up\n"); 1047 StartLastPositionTimer(); 1048 } 1049 } else if (mClientState == ClientState::SettingAccuracy) { 1050 GCLP_SETSTATE(this, SettingAccuracyForStart); 1051 } else if (mClientState == ClientState::Stopping) { 1052 GCLP_SETSTATE(this, StoppingForRestart); 1053 } 1054 } 1055 1056 NS_IMETHODIMP 1057 GCLocProviderPriv::Watch(nsIGeolocationUpdate* aCallback) { 1058 mCallback = aCallback; 1059 1060 if (!mCancellable) { 1061 mCancellable = dont_AddRef(g_cancellable_new()); 1062 } 1063 1064 if (mClientState != ClientState::Uninit) { 1065 WatchStart(); 1066 return NS_OK; 1067 } 1068 1069 if (!mProxyManager) { 1070 GCL_LOG(Debug, "watch request falling back to MLS"); 1071 return FallbackToMLS(MLSFallback::FallbackReason::Error); 1072 } 1073 1074 StopMLSFallback(); 1075 1076 GCLP_SETSTATE(this, Initing); 1077 nsAppShell::DBusConnectionCheck(); 1078 g_dbus_proxy_call(mProxyManager, "GetClient", nullptr, G_DBUS_CALL_FLAGS_NONE, 1079 -1, mCancellable, 1080 reinterpret_cast<GAsyncReadyCallback>(GetClientResponse), 1081 this); 1082 1083 return NS_OK; 1084 } 1085 1086 NS_IMETHODIMP 1087 GCLocProviderPriv::Shutdown() { 1088 DoShutdownClearCallback(false); 1089 return NS_OK; 1090 } 1091 1092 NS_IMETHODIMP 1093 GCLocProviderPriv::SetHighAccuracy(bool aHigh) { 1094 GCL_LOG(Verbose, "Want %s accuracy\n", aHigh ? "high" : "low"); 1095 if (!aHigh && AlwaysHighAccuracy()) { 1096 GCL_LOG(Verbose, "Forcing high accuracy due to pref\n"); 1097 aHigh = true; 1098 } 1099 1100 mAccuracyWanted = aHigh ? Accuracy::High : Accuracy::Low; 1101 MaybeRestartForAccuracy(); 1102 1103 return NS_OK; 1104 } 1105 1106 GeoclueLocationProvider::GeoclueLocationProvider() { 1107 mPriv = new GCLocProviderPriv; 1108 } 1109 1110 // nsISupports 1111 // 1112 1113 NS_IMPL_ISUPPORTS(GeoclueLocationProvider, nsIGeolocationProvider) 1114 1115 // nsIGeolocationProvider 1116 // 1117 1118 NS_IMETHODIMP 1119 GeoclueLocationProvider::Startup() { return mPriv->Startup(); } 1120 1121 NS_IMETHODIMP 1122 GeoclueLocationProvider::Watch(nsIGeolocationUpdate* aCallback) { 1123 return mPriv->Watch(aCallback); 1124 } 1125 1126 NS_IMETHODIMP 1127 GeoclueLocationProvider::Shutdown() { return mPriv->Shutdown(); } 1128 1129 NS_IMETHODIMP 1130 GeoclueLocationProvider::SetHighAccuracy(bool aHigh) { 1131 return mPriv->SetHighAccuracy(aHigh); 1132 } 1133 1134 } // namespace mozilla::dom