/* * 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>
*/
/* * Whether to reuse D-Bus proxies between uses of this provider. * Usually a good thing, can be disabled for debug purposes.
*/ staticconstbool kGCReuseDBusProxy = true;
class GCLocProviderPriv final : public nsIGeolocationProvider, public SupportsWeakPtr { public:
NS_DECL_ISUPPORTS
NS_DECL_NSIGEOLOCATIONPROVIDER
GCLocProviderPriv();
private: enumclass 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) enumclass ClientState {
Uninit,
Initing,
SettingAccuracy,
SettingAccuracyForStart,
Idle,
Starting,
Started,
Stopping,
StoppingForRestart
};
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"); if (mMLSFallbackTimer) { // We are not going to wait for MLS fallback anymore
glean::geolocation::fallback
.EnumGet(glean::geolocation::FallbackLabel::eNone)
.Add();
}
StopPositionTimer();
StopMLSFallbackTimer();
Update(mLastPosition);
}
nsresult GCLocProviderPriv::FallbackToMLS(MLSFallback::FallbackReason aReason) {
GCL_LOG(Debug, "trying to fall back to MLS");
StopMLSFallback();
RefPtr fallback = new MLSFallback(0);
MOZ_TRY(fallback->Startup(mCallback, aReason));
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");
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); // If we're started, and we don't get any location update in a reasonable // amount of time, we fallback to MLS.
self->StartMLSFallbackTimerIfNeeded();
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");
void GCLocProviderPriv::StartMLSFallbackTimerIfNeeded() {
StopMLSFallbackTimer(); if (mLastPosition) { // If we already have a location we're good. return;
}
uint32_t delay = StaticPrefs::geo_provider_geoclue_mls_fallback_timeout_ms(); if (!delay) { return;
}
GCL_LOG(Info, "Didn't get a location in a reasonable amount of time, trying to " "fall back to MLS");
FallbackToMLS(MLSFallback::FallbackReason::Timeout);
}
// 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;
}
/* * 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;
}
// Invalidate the cached last position
StopPositionTimer();
StopMLSFallbackTimer();
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();
}
/* * 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;
}
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;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.