diff options
Diffstat (limited to '')
-rw-r--r-- | dom/geolocation/Geolocation.cpp | 1221 | ||||
-rw-r--r-- | dom/geolocation/Geolocation.h | 249 | ||||
-rw-r--r-- | dom/geolocation/GeolocationCoordinates.cpp | 67 | ||||
-rw-r--r-- | dom/geolocation/GeolocationCoordinates.h | 56 | ||||
-rw-r--r-- | dom/geolocation/GeolocationPosition.cpp | 186 | ||||
-rw-r--r-- | dom/geolocation/GeolocationPosition.h | 95 | ||||
-rw-r--r-- | dom/geolocation/GeolocationPositionError.cpp | 64 | ||||
-rw-r--r-- | dom/geolocation/GeolocationPositionError.h | 51 | ||||
-rw-r--r-- | dom/geolocation/MLSFallback.cpp | 78 | ||||
-rw-r--r-- | dom/geolocation/MLSFallback.h | 48 | ||||
-rw-r--r-- | dom/geolocation/moz.build | 60 | ||||
-rw-r--r-- | dom/geolocation/nsGeoPositionIPCSerialiser.h | 134 |
12 files changed, 2309 insertions, 0 deletions
diff --git a/dom/geolocation/Geolocation.cpp b/dom/geolocation/Geolocation.cpp new file mode 100644 index 0000000000..cb9107deb1 --- /dev/null +++ b/dom/geolocation/Geolocation.cpp @@ -0,0 +1,1221 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "Geolocation.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/CycleCollectedJSContext.h" // for nsAutoMicroTask +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/PermissionMessageUtils.h" +#include "mozilla/dom/GeolocationPositionError.h" +#include "mozilla/dom/GeolocationPositionErrorBinding.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_geo.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "mozilla/WeakPtr.h" +#include "mozilla/EventStateManager.h" +#include "nsComponentManagerUtils.h" +#include "nsContentPermissionHelper.h" +#include "nsContentUtils.h" +#include "nsGlobalWindowInner.h" +#include "mozilla/dom/Document.h" +#include "nsINamed.h" +#include "nsIObserverService.h" +#include "nsIScriptError.h" +#include "nsPIDOMWindow.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +class nsIPrincipal; + +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidLocationProvider.h" +#endif + +#ifdef MOZ_GPSD +# include "GpsdLocationProvider.h" +#endif + +#ifdef MOZ_ENABLE_DBUS +# include "mozilla/WidgetUtilsGtk.h" +# include "GeoclueLocationProvider.h" +# include "PortalLocationProvider.h" +#endif + +#ifdef MOZ_WIDGET_COCOA +# include "CoreLocationLocationProvider.h" +#endif + +#ifdef XP_WIN +# include "WindowsLocationProvider.h" +#endif + +// Some limit to the number of get or watch geolocation requests +// that a window can make. +#define MAX_GEO_REQUESTS_PER_WINDOW 1500 + +// This preference allows to override the "secure context" by +// default policy. +#define PREF_GEO_SECURITY_ALLOWINSECURE "geo.security.allowinsecure" + +using mozilla::Unused; // <snicker> +using namespace mozilla; +using namespace mozilla::dom; + +class nsGeolocationRequest final : public ContentPermissionRequestBase, + public nsIGeolocationUpdate, + public SupportsWeakPtr { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIGEOLOCATIONUPDATE + + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsGeolocationRequest, + ContentPermissionRequestBase) + + nsGeolocationRequest(Geolocation* aLocator, GeoPositionCallback aCallback, + GeoPositionErrorCallback aErrorCallback, + UniquePtr<PositionOptions>&& aOptions, + nsIEventTarget* aMainThreadSerialEventTarget, + bool aWatchPositionRequest = false, + int32_t aWatchId = 0); + + // nsIContentPermissionRequest + MOZ_CAN_RUN_SCRIPT NS_IMETHOD Cancel(void) override; + MOZ_CAN_RUN_SCRIPT NS_IMETHOD Allow(JS::Handle<JS::Value> choices) override; + + void Shutdown(); + + // MOZ_CAN_RUN_SCRIPT_BOUNDARY is OK here because we're always called from a + // runnable. Ideally nsIRunnable::Run and its overloads would just be + // MOZ_CAN_RUN_SCRIPT and then we could be too... + MOZ_CAN_RUN_SCRIPT_BOUNDARY + void SendLocation(nsIDOMGeoPosition* aLocation); + bool WantsHighAccuracy() { + return !mShutdown && mOptions && mOptions->mEnableHighAccuracy; + } + void SetTimeoutTimer(); + void StopTimeoutTimer(); + MOZ_CAN_RUN_SCRIPT + void NotifyErrorAndShutdown(uint16_t); + using ContentPermissionRequestBase::GetPrincipal; + nsIPrincipal* GetPrincipal(); + + bool IsWatch() { return mIsWatchPositionRequest; } + int32_t WatchId() { return mWatchId; } + + private: + virtual ~nsGeolocationRequest(); + + class TimerCallbackHolder final : public nsITimerCallback, public nsINamed { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + + explicit TimerCallbackHolder(nsGeolocationRequest* aRequest) + : mRequest(aRequest) {} + + NS_IMETHOD GetName(nsACString& aName) override { + aName.AssignLiteral("nsGeolocationRequest::TimerCallbackHolder"); + return NS_OK; + } + + private: + ~TimerCallbackHolder() = default; + WeakPtr<nsGeolocationRequest> mRequest; + }; + + // Only called from a timer, so MOZ_CAN_RUN_SCRIPT_BOUNDARY ok for now. + MOZ_CAN_RUN_SCRIPT_BOUNDARY void Notify(); + + bool mIsWatchPositionRequest; + + nsCOMPtr<nsITimer> mTimeoutTimer; + GeoPositionCallback mCallback; + GeoPositionErrorCallback mErrorCallback; + UniquePtr<PositionOptions> mOptions; + + RefPtr<Geolocation> mLocator; + + int32_t mWatchId; + bool mShutdown; + nsCOMPtr<nsIEventTarget> mMainThreadSerialEventTarget; +}; + +static UniquePtr<PositionOptions> CreatePositionOptionsCopy( + const PositionOptions& aOptions) { + UniquePtr<PositionOptions> geoOptions = MakeUnique<PositionOptions>(); + + geoOptions->mEnableHighAccuracy = aOptions.mEnableHighAccuracy; + geoOptions->mMaximumAge = aOptions.mMaximumAge; + geoOptions->mTimeout = aOptions.mTimeout; + + return geoOptions; +} + +class RequestSendLocationEvent : public Runnable { + public: + RequestSendLocationEvent(nsIDOMGeoPosition* aPosition, + nsGeolocationRequest* aRequest) + : mozilla::Runnable("RequestSendLocationEvent"), + mPosition(aPosition), + mRequest(aRequest) {} + + NS_IMETHOD Run() override { + mRequest->SendLocation(mPosition); + return NS_OK; + } + + private: + nsCOMPtr<nsIDOMGeoPosition> mPosition; + RefPtr<nsGeolocationRequest> mRequest; + RefPtr<Geolocation> mLocator; +}; + +//////////////////////////////////////////////////// +// nsGeolocationRequest +//////////////////////////////////////////////////// + +static nsPIDOMWindowInner* ConvertWeakReferenceToWindow( + nsIWeakReference* aWeakPtr) { + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(aWeakPtr); + // This isn't usually safe, but here we're just extracting a raw pointer in + // order to pass it to a base class constructor which will in turn convert it + // into a strong pointer for us. + nsPIDOMWindowInner* raw = window.get(); + return raw; +} + +nsGeolocationRequest::nsGeolocationRequest( + Geolocation* aLocator, GeoPositionCallback aCallback, + GeoPositionErrorCallback aErrorCallback, + UniquePtr<PositionOptions>&& aOptions, + nsIEventTarget* aMainThreadSerialEventTarget, bool aWatchPositionRequest, + int32_t aWatchId) + : ContentPermissionRequestBase( + aLocator->GetPrincipal(), + ConvertWeakReferenceToWindow(aLocator->GetOwner()), "geo"_ns, + "geolocation"_ns), + mIsWatchPositionRequest(aWatchPositionRequest), + mCallback(std::move(aCallback)), + mErrorCallback(std::move(aErrorCallback)), + mOptions(std::move(aOptions)), + mLocator(aLocator), + mWatchId(aWatchId), + mShutdown(false), + mMainThreadSerialEventTarget(aMainThreadSerialEventTarget) {} + +nsGeolocationRequest::~nsGeolocationRequest() { StopTimeoutTimer(); } + +NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(nsGeolocationRequest, + ContentPermissionRequestBase, + nsIGeolocationUpdate) + +NS_IMPL_ADDREF_INHERITED(nsGeolocationRequest, ContentPermissionRequestBase) +NS_IMPL_RELEASE_INHERITED(nsGeolocationRequest, ContentPermissionRequestBase) +NS_IMPL_CYCLE_COLLECTION_WEAK_PTR_INHERITED(nsGeolocationRequest, + ContentPermissionRequestBase, + mCallback, mErrorCallback, mLocator) + +void nsGeolocationRequest::Notify() { + SetTimeoutTimer(); + NotifyErrorAndShutdown(GeolocationPositionError_Binding::TIMEOUT); +} + +void nsGeolocationRequest::NotifyErrorAndShutdown(uint16_t aErrorCode) { + MOZ_ASSERT(!mShutdown, "timeout after shutdown"); + if (!mIsWatchPositionRequest) { + Shutdown(); + mLocator->RemoveRequest(this); + } + + NotifyError(aErrorCode); +} + +NS_IMETHODIMP +nsGeolocationRequest::Cancel() { + if (mLocator->ClearPendingRequest(this)) { + return NS_OK; + } + + NotifyError(GeolocationPositionError_Binding::PERMISSION_DENIED); + return NS_OK; +} + +NS_IMETHODIMP +nsGeolocationRequest::Allow(JS::Handle<JS::Value> aChoices) { + MOZ_ASSERT(aChoices.isUndefined()); + + if (mLocator->ClearPendingRequest(this)) { + return NS_OK; + } + + RefPtr<nsGeolocationService> gs = + nsGeolocationService::GetGeolocationService(); + + bool canUseCache = false; + CachedPositionAndAccuracy lastPosition = gs->GetCachedPosition(); + if (lastPosition.position) { + EpochTimeStamp cachedPositionTime_ms; + lastPosition.position->GetTimestamp(&cachedPositionTime_ms); + // check to see if we can use a cached value + // if the user has specified a maximumAge, return a cached value. + if (mOptions && mOptions->mMaximumAge > 0) { + uint32_t maximumAge_ms = mOptions->mMaximumAge; + bool isCachedWithinRequestedAccuracy = + WantsHighAccuracy() <= lastPosition.isHighAccuracy; + bool isCachedWithinRequestedTime = + EpochTimeStamp(PR_Now() / PR_USEC_PER_MSEC - maximumAge_ms) <= + cachedPositionTime_ms; + canUseCache = + isCachedWithinRequestedAccuracy && isCachedWithinRequestedTime; + } + } + + gs->UpdateAccuracy(WantsHighAccuracy()); + if (canUseCache) { + // okay, we can return a cached position + // getCurrentPosition requests serviced by the cache + // will now be owned by the RequestSendLocationEvent + Update(lastPosition.position); + + // After Update is called, getCurrentPosition finishes it's job. + if (!mIsWatchPositionRequest) { + return NS_OK; + } + + } else { + // if it is not a watch request and timeout is 0, + // invoke the errorCallback (if present) with TIMEOUT code + if (mOptions && mOptions->mTimeout == 0 && !mIsWatchPositionRequest) { + NotifyError(GeolocationPositionError_Binding::TIMEOUT); + return NS_OK; + } + } + + // Non-cached location request + bool allowedRequest = mIsWatchPositionRequest || !canUseCache; + if (allowedRequest) { + // let the locator know we're pending + // we will now be owned by the locator + mLocator->NotifyAllowedRequest(this); + } + + // Kick off the geo device, if it isn't already running + nsresult rv = gs->StartDevice(); + + if (NS_FAILED(rv)) { + if (allowedRequest) { + mLocator->RemoveRequest(this); + } + // Location provider error + NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE); + return NS_OK; + } + + SetTimeoutTimer(); + + return NS_OK; +} + +void nsGeolocationRequest::SetTimeoutTimer() { + MOZ_ASSERT(!mShutdown, "set timeout after shutdown"); + + StopTimeoutTimer(); + + if (mOptions && mOptions->mTimeout != 0 && mOptions->mTimeout != 0x7fffffff) { + RefPtr<TimerCallbackHolder> holder = new TimerCallbackHolder(this); + NS_NewTimerWithCallback(getter_AddRefs(mTimeoutTimer), holder, + mOptions->mTimeout, nsITimer::TYPE_ONE_SHOT); + } +} + +void nsGeolocationRequest::StopTimeoutTimer() { + if (mTimeoutTimer) { + mTimeoutTimer->Cancel(); + mTimeoutTimer = nullptr; + } +} + +void nsGeolocationRequest::SendLocation(nsIDOMGeoPosition* aPosition) { + if (mShutdown) { + // Ignore SendLocationEvents issued before we were cleared. + return; + } + + if (mOptions && mOptions->mMaximumAge > 0) { + EpochTimeStamp positionTime_ms; + aPosition->GetTimestamp(&positionTime_ms); + const uint32_t maximumAge_ms = mOptions->mMaximumAge; + const bool isTooOld = EpochTimeStamp(PR_Now() / PR_USEC_PER_MSEC - + maximumAge_ms) > positionTime_ms; + if (isTooOld) { + return; + } + } + + RefPtr<mozilla::dom::GeolocationPosition> wrapped; + + if (aPosition) { + nsCOMPtr<nsIDOMGeoPositionCoords> coords; + aPosition->GetCoords(getter_AddRefs(coords)); + if (coords) { + wrapped = new mozilla::dom::GeolocationPosition(ToSupports(mLocator), + aPosition); + } + } + + if (!wrapped) { + NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE); + return; + } + + if (!mIsWatchPositionRequest) { + // Cancel timer and position updates in case the position + // callback spins the event loop + Shutdown(); + } + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + obs->NotifyObservers(wrapped, "geolocation-position-events", + u"location-updated"); + + nsAutoMicroTask mt; + if (mCallback.HasWebIDLCallback()) { + RefPtr<PositionCallback> callback = mCallback.GetWebIDLCallback(); + + MOZ_ASSERT(callback); + callback->Call(*wrapped); + } else { + nsIDOMGeoPositionCallback* callback = mCallback.GetXPCOMCallback(); + MOZ_ASSERT(callback); + callback->HandleEvent(aPosition); + } + + if (mIsWatchPositionRequest && !mShutdown) { + SetTimeoutTimer(); + } + MOZ_ASSERT(mShutdown || mIsWatchPositionRequest, + "non-shutdown getCurrentPosition request after callback!"); +} + +nsIPrincipal* nsGeolocationRequest::GetPrincipal() { + if (!mLocator) { + return nullptr; + } + return mLocator->GetPrincipal(); +} + +NS_IMETHODIMP +nsGeolocationRequest::Update(nsIDOMGeoPosition* aPosition) { + nsCOMPtr<nsIRunnable> ev = new RequestSendLocationEvent(aPosition, this); + mMainThreadSerialEventTarget->Dispatch(ev.forget()); + return NS_OK; +} + +NS_IMETHODIMP +nsGeolocationRequest::NotifyError(uint16_t aErrorCode) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<GeolocationPositionError> positionError = + new GeolocationPositionError(mLocator, aErrorCode); + positionError->NotifyCallback(mErrorCallback); + return NS_OK; +} + +void nsGeolocationRequest::Shutdown() { + MOZ_ASSERT(!mShutdown, "request shutdown twice"); + mShutdown = true; + + StopTimeoutTimer(); + + // If there are no other high accuracy requests, the geolocation service will + // notify the provider to switch to the default accuracy. + if (mOptions && mOptions->mEnableHighAccuracy) { + RefPtr<nsGeolocationService> gs = + nsGeolocationService::GetGeolocationService(); + if (gs) { + gs->UpdateAccuracy(); + } + } +} + +//////////////////////////////////////////////////// +// nsGeolocationRequest::TimerCallbackHolder +//////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder, nsITimerCallback, + nsINamed) + +NS_IMETHODIMP +nsGeolocationRequest::TimerCallbackHolder::Notify(nsITimer*) { + if (mRequest && mRequest->mLocator) { + RefPtr<nsGeolocationRequest> request(mRequest); + request->Notify(); + } + + return NS_OK; +} + +//////////////////////////////////////////////////// +// nsGeolocationService +//////////////////////////////////////////////////// +NS_INTERFACE_MAP_BEGIN(nsGeolocationService) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIGeolocationUpdate) + NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate) + NS_INTERFACE_MAP_ENTRY(nsIObserver) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsGeolocationService) +NS_IMPL_RELEASE(nsGeolocationService) + +nsresult nsGeolocationService::Init() { + if (!StaticPrefs::geo_enabled()) { + return NS_ERROR_FAILURE; + } + + if (XRE_IsContentProcess()) { + return NS_OK; + } + + // geolocation service can be enabled -> now register observer + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (!obs) { + return NS_ERROR_FAILURE; + } + + obs->AddObserver(this, "xpcom-shutdown", false); + +#ifdef MOZ_WIDGET_ANDROID + mProvider = new AndroidLocationProvider(); +#endif + +#ifdef MOZ_WIDGET_GTK +# ifdef MOZ_ENABLE_DBUS + if (!mProvider && widget::ShouldUsePortal(widget::PortalKind::Location)) { + mProvider = new PortalLocationProvider(); + } + // Geoclue includes GPS data so it has higher priority than raw GPSD + if (!mProvider && StaticPrefs::geo_provider_use_geoclue()) { + nsCOMPtr<nsIGeolocationProvider> gcProvider = new GeoclueLocationProvider(); + // The Startup() method will only succeed if Geoclue is available on D-Bus + if (NS_SUCCEEDED(gcProvider->Startup())) { + gcProvider->Shutdown(); + mProvider = std::move(gcProvider); + } + } +# ifdef MOZ_GPSD + if (!mProvider && Preferences::GetBool("geo.provider.use_gpsd", false)) { + mProvider = new GpsdLocationProvider(); + } +# endif +# endif +#endif + +#ifdef MOZ_WIDGET_COCOA + if (Preferences::GetBool("geo.provider.use_corelocation", true)) { + mProvider = new CoreLocationLocationProvider(); + } +#endif + +#ifdef XP_WIN + if (Preferences::GetBool("geo.provider.ms-windows-location", false)) { + mProvider = new WindowsLocationProvider(); + } +#endif + + if (Preferences::GetBool("geo.provider.use_mls", false)) { + mProvider = do_CreateInstance("@mozilla.org/geolocation/mls-provider;1"); + } + + // Override platform-specific providers with the default (network) + // provider while testing. Our tests are currently not meant to exercise + // the provider, and some tests rely on the network provider being used. + // "geo.provider.testing" is always set for all plain and browser chrome + // mochitests, and also for xpcshell tests. + if (!mProvider || Preferences::GetBool("geo.provider.testing", false)) { + nsCOMPtr<nsIGeolocationProvider> geoTestProvider = + do_GetService(NS_GEOLOCATION_PROVIDER_CONTRACTID); + + if (geoTestProvider) { + mProvider = geoTestProvider; + } + } + + return NS_OK; +} + +nsGeolocationService::~nsGeolocationService() = default; + +NS_IMETHODIMP +nsGeolocationService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp("xpcom-shutdown", aTopic)) { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "xpcom-shutdown"); + } + + for (uint32_t i = 0; i < mGeolocators.Length(); i++) { + mGeolocators[i]->Shutdown(); + } + StopDevice(); + + return NS_OK; + } + + if (!strcmp("timer-callback", aTopic)) { + // decide if we can close down the service. + for (uint32_t i = 0; i < mGeolocators.Length(); i++) + if (mGeolocators[i]->HasActiveCallbacks()) { + SetDisconnectTimer(); + return NS_OK; + } + + // okay to close up. + StopDevice(); + Update(nullptr); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsGeolocationService::Update(nsIDOMGeoPosition* aSomewhere) { + if (aSomewhere) { + SetCachedPosition(aSomewhere); + } + + for (uint32_t i = 0; i < mGeolocators.Length(); i++) { + mGeolocators[i]->Update(aSomewhere); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsGeolocationService::NotifyError(uint16_t aErrorCode) { + // nsTArray doesn't have a constructors that takes a different-type TArray. + nsTArray<RefPtr<Geolocation>> geolocators; + geolocators.AppendElements(mGeolocators); + for (uint32_t i = 0; i < geolocators.Length(); i++) { + // MOZ_KnownLive because the stack array above keeps it alive. + MOZ_KnownLive(geolocators[i])->NotifyError(aErrorCode); + } + return NS_OK; +} + +void nsGeolocationService::SetCachedPosition(nsIDOMGeoPosition* aPosition) { + mLastPosition.position = aPosition; + mLastPosition.isHighAccuracy = mHigherAccuracy; +} + +CachedPositionAndAccuracy nsGeolocationService::GetCachedPosition() { + return mLastPosition; +} + +nsresult nsGeolocationService::StartDevice() { + if (!StaticPrefs::geo_enabled()) { + return NS_ERROR_NOT_AVAILABLE; + } + + // We do not want to keep the geolocation devices online + // indefinitely. + // Close them down after a reasonable period of inactivivity. + SetDisconnectTimer(); + + if (XRE_IsContentProcess()) { + ContentChild* cpc = ContentChild::GetSingleton(); + cpc->SendAddGeolocationListener(HighAccuracyRequested()); + return NS_OK; + } + + // Start them up! + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (!obs) { + return NS_ERROR_FAILURE; + } + + if (!mProvider) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + + if (NS_FAILED(rv = mProvider->Startup()) || + NS_FAILED(rv = mProvider->Watch(this))) { + NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE); + return rv; + } + + obs->NotifyObservers(mProvider, "geolocation-device-events", u"starting"); + + return NS_OK; +} + +void nsGeolocationService::SetDisconnectTimer() { + if (!mDisconnectTimer) { + mDisconnectTimer = NS_NewTimer(); + } else { + mDisconnectTimer->Cancel(); + } + + mDisconnectTimer->Init(this, StaticPrefs::geo_timeout(), + nsITimer::TYPE_ONE_SHOT); +} + +bool nsGeolocationService::HighAccuracyRequested() { + for (uint32_t i = 0; i < mGeolocators.Length(); i++) { + if (mGeolocators[i]->HighAccuracyRequested()) { + return true; + } + } + + return false; +} + +void nsGeolocationService::UpdateAccuracy(bool aForceHigh) { + bool highRequired = aForceHigh || HighAccuracyRequested(); + + if (XRE_IsContentProcess()) { + ContentChild* cpc = ContentChild::GetSingleton(); + if (cpc->IsAlive()) { + cpc->SendSetGeolocationHigherAccuracy(highRequired); + } + + return; + } + + mProvider->SetHighAccuracy(!mHigherAccuracy && highRequired); + mHigherAccuracy = highRequired; +} + +void nsGeolocationService::StopDevice() { + if (mDisconnectTimer) { + mDisconnectTimer->Cancel(); + mDisconnectTimer = nullptr; + } + + if (XRE_IsContentProcess()) { + ContentChild* cpc = ContentChild::GetSingleton(); + cpc->SendRemoveGeolocationListener(); + + return; // bail early + } + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (!obs) { + return; + } + + if (!mProvider) { + return; + } + + mHigherAccuracy = false; + + mProvider->Shutdown(); + obs->NotifyObservers(mProvider, "geolocation-device-events", u"shutdown"); +} + +StaticRefPtr<nsGeolocationService> nsGeolocationService::sService; + +already_AddRefed<nsGeolocationService> +nsGeolocationService::GetGeolocationService() { + RefPtr<nsGeolocationService> result; + if (nsGeolocationService::sService) { + result = nsGeolocationService::sService; + + return result.forget(); + } + + result = new nsGeolocationService(); + if (NS_FAILED(result->Init())) { + return nullptr; + } + + ClearOnShutdown(&nsGeolocationService::sService); + nsGeolocationService::sService = result; + return result.forget(); +} + +void nsGeolocationService::AddLocator(Geolocation* aLocator) { + mGeolocators.AppendElement(aLocator); +} + +void nsGeolocationService::RemoveLocator(Geolocation* aLocator) { + mGeolocators.RemoveElement(aLocator); +} + +//////////////////////////////////////////////////// +// Geolocation +//////////////////////////////////////////////////// + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Geolocation) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(Geolocation) +NS_IMPL_CYCLE_COLLECTING_RELEASE(Geolocation) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Geolocation, mPendingCallbacks, + mWatchingCallbacks, mPendingRequests) + +Geolocation::Geolocation() + : mProtocolType(ProtocolType::OTHER), mLastWatchId(1) {} + +Geolocation::~Geolocation() { + if (mService) { + Shutdown(); + } +} + +StaticRefPtr<Geolocation> Geolocation::sNonWindowSingleton; + +already_AddRefed<Geolocation> Geolocation::NonWindowSingleton() { + if (sNonWindowSingleton) { + return do_AddRef(sNonWindowSingleton); + } + + RefPtr<Geolocation> result = new Geolocation(); + DebugOnly<nsresult> rv = result->Init(); + MOZ_ASSERT(NS_SUCCEEDED(rv), "How can this fail?"); + + ClearOnShutdown(&sNonWindowSingleton); + sNonWindowSingleton = result; + return result.forget(); +} + +nsresult Geolocation::Init(nsPIDOMWindowInner* aContentDom) { + // Remember the window + if (aContentDom) { + mOwner = do_GetWeakReference(aContentDom); + if (!mOwner) { + return NS_ERROR_FAILURE; + } + + // Grab the principal of the document + nsCOMPtr<Document> doc = aContentDom->GetDoc(); + if (!doc) { + return NS_ERROR_FAILURE; + } + + mPrincipal = doc->NodePrincipal(); + // Store the protocol to send via telemetry later. + if (mPrincipal->SchemeIs("http")) { + mProtocolType = ProtocolType::HTTP; + } else if (mPrincipal->SchemeIs("https")) { + mProtocolType = ProtocolType::HTTPS; + } + } + + // If no aContentDom was passed into us, we are being used + // by chrome/c++ and have no mOwner, no mPrincipal, and no need + // to prompt. + mService = nsGeolocationService::GetGeolocationService(); + if (mService) { + mService->AddLocator(this); + } + + return NS_OK; +} + +void Geolocation::Shutdown() { + // Release all callbacks + mPendingCallbacks.Clear(); + mWatchingCallbacks.Clear(); + + if (mService) { + mService->RemoveLocator(this); + mService->UpdateAccuracy(); + } + + mService = nullptr; + mPrincipal = nullptr; +} + +nsPIDOMWindowInner* Geolocation::GetParentObject() const { + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mOwner); + return window.get(); +} + +bool Geolocation::HasActiveCallbacks() { + return mPendingCallbacks.Length() || mWatchingCallbacks.Length(); +} + +bool Geolocation::HighAccuracyRequested() { + for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) { + if (mWatchingCallbacks[i]->WantsHighAccuracy()) { + return true; + } + } + + for (uint32_t i = 0; i < mPendingCallbacks.Length(); i++) { + if (mPendingCallbacks[i]->WantsHighAccuracy()) { + return true; + } + } + + return false; +} + +void Geolocation::RemoveRequest(nsGeolocationRequest* aRequest) { + bool requestWasKnown = (mPendingCallbacks.RemoveElement(aRequest) != + mWatchingCallbacks.RemoveElement(aRequest)); + + Unused << requestWasKnown; +} + +NS_IMETHODIMP +Geolocation::Update(nsIDOMGeoPosition* aSomewhere) { + if (!WindowOwnerStillExists()) { + Shutdown(); + return NS_OK; + } + + // Don't update position if window is not fully active or the document is + // hidden. We keep the pending callaback and watchers waiting for the next + // update. + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(this->GetOwner()); + if (window) { + nsCOMPtr<Document> document = window->GetDoc(); + bool isHidden = document && document->Hidden(); + if (isHidden || !window->IsFullyActive()) { + return NS_OK; + } + } + + if (aSomewhere) { + nsCOMPtr<nsIDOMGeoPositionCoords> coords; + aSomewhere->GetCoords(getter_AddRefs(coords)); + if (coords) { + double accuracy = -1; + coords->GetAccuracy(&accuracy); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::GEOLOCATION_ACCURACY_EXPONENTIAL, accuracy); + } + } + + for (uint32_t i = mPendingCallbacks.Length(); i > 0; i--) { + mPendingCallbacks[i - 1]->Update(aSomewhere); + RemoveRequest(mPendingCallbacks[i - 1]); + } + + // notify everyone that is watching + for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) { + mWatchingCallbacks[i]->Update(aSomewhere); + } + + return NS_OK; +} + +NS_IMETHODIMP +Geolocation::NotifyError(uint16_t aErrorCode) { + if (!WindowOwnerStillExists()) { + Shutdown(); + return NS_OK; + } + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::GEOLOCATION_ERROR, true); + + for (uint32_t i = mPendingCallbacks.Length(); i > 0; i--) { + RefPtr<nsGeolocationRequest> request = mPendingCallbacks[i - 1]; + request->NotifyErrorAndShutdown(aErrorCode); + // NotifyErrorAndShutdown() removes the request from the array + } + + // notify everyone that is watching + for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) { + RefPtr<nsGeolocationRequest> request = mWatchingCallbacks[i]; + request->NotifyErrorAndShutdown(aErrorCode); + } + + return NS_OK; +} + +bool Geolocation::IsFullyActiveOrChrome() { + // For regular content window, only allow this proceed if the window is "fully + // active". + if (nsPIDOMWindowInner* window = this->GetParentObject()) { + return window->IsFullyActive(); + } + // Calls coming from chrome code don't have window, so we can proceed. + return true; +} + +bool Geolocation::IsAlreadyCleared(nsGeolocationRequest* aRequest) { + for (uint32_t i = 0, length = mClearedWatchIDs.Length(); i < length; ++i) { + if (mClearedWatchIDs[i] == aRequest->WatchId()) { + return true; + } + } + + return false; +} + +bool Geolocation::ShouldBlockInsecureRequests() const { + if (Preferences::GetBool(PREF_GEO_SECURITY_ALLOWINSECURE, false)) { + return false; + } + + nsCOMPtr<nsPIDOMWindowInner> win = do_QueryReferent(mOwner); + if (!win) { + return false; + } + + nsCOMPtr<Document> doc = win->GetDoc(); + if (!doc) { + return false; + } + + if (!nsGlobalWindowInner::Cast(win)->IsSecureContext()) { + nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns, doc, + nsContentUtils::eDOM_PROPERTIES, + "GeolocationInsecureRequestIsForbidden"); + return true; + } + + return false; +} + +bool Geolocation::ClearPendingRequest(nsGeolocationRequest* aRequest) { + if (aRequest->IsWatch() && this->IsAlreadyCleared(aRequest)) { + this->NotifyAllowedRequest(aRequest); + this->ClearWatch(aRequest->WatchId()); + return true; + } + + return false; +} + +void Geolocation::GetCurrentPosition(PositionCallback& aCallback, + PositionErrorCallback* aErrorCallback, + const PositionOptions& aOptions, + CallerType aCallerType, ErrorResult& aRv) { + nsresult rv = GetCurrentPosition( + GeoPositionCallback(&aCallback), GeoPositionErrorCallback(aErrorCallback), + CreatePositionOptionsCopy(aOptions), aCallerType); + + if (NS_FAILED(rv)) { + aRv.Throw(rv); + } +} + +nsresult Geolocation::GetCurrentPosition(GeoPositionCallback callback, + GeoPositionErrorCallback errorCallback, + UniquePtr<PositionOptions>&& options, + CallerType aCallerType) { + if (!IsFullyActiveOrChrome()) { + RefPtr<GeolocationPositionError> positionError = + new GeolocationPositionError( + this, GeolocationPositionError_Binding::POSITION_UNAVAILABLE); + positionError->NotifyCallback(errorCallback); + return NS_OK; + } + + if (mPendingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) { + return NS_ERROR_NOT_AVAILABLE; + } + + // After this we hand over ownership of options to our nsGeolocationRequest. + + nsIEventTarget* target = GetMainThreadSerialEventTarget(); + RefPtr<nsGeolocationRequest> request = new nsGeolocationRequest( + this, std::move(callback), std::move(errorCallback), std::move(options), + target); + + if (!StaticPrefs::geo_enabled() || ShouldBlockInsecureRequests() || + !request->CheckPermissionDelegate()) { + request->RequestDelayedTask(target, + nsGeolocationRequest::DelayedTaskType::Deny); + return NS_OK; + } + + if (!mOwner && aCallerType != CallerType::System) { + return NS_ERROR_FAILURE; + } + + if (mOwner) { + if (!RegisterRequestWithPrompt(request)) { + return NS_ERROR_NOT_AVAILABLE; + } + + return NS_OK; + } + + if (aCallerType != CallerType::System) { + return NS_ERROR_FAILURE; + } + + request->RequestDelayedTask(target, + nsGeolocationRequest::DelayedTaskType::Allow); + + return NS_OK; +} + +int32_t Geolocation::WatchPosition(PositionCallback& aCallback, + PositionErrorCallback* aErrorCallback, + const PositionOptions& aOptions, + CallerType aCallerType, ErrorResult& aRv) { + return WatchPosition(GeoPositionCallback(&aCallback), + GeoPositionErrorCallback(aErrorCallback), + CreatePositionOptionsCopy(aOptions), aCallerType, aRv); +} + +int32_t Geolocation::WatchPosition( + nsIDOMGeoPositionCallback* aCallback, + nsIDOMGeoPositionErrorCallback* aErrorCallback, + UniquePtr<PositionOptions>&& aOptions) { + MOZ_ASSERT(aCallback); + + return WatchPosition(GeoPositionCallback(aCallback), + GeoPositionErrorCallback(aErrorCallback), + std::move(aOptions), CallerType::System, IgnoreErrors()); +} + +// On errors we return 0 because that's not a valid watch id and will +// get ignored in clearWatch. +int32_t Geolocation::WatchPosition(GeoPositionCallback aCallback, + GeoPositionErrorCallback aErrorCallback, + UniquePtr<PositionOptions>&& aOptions, + CallerType aCallerType, ErrorResult& aRv) { + if (!IsFullyActiveOrChrome()) { + RefPtr<GeolocationPositionError> positionError = + new GeolocationPositionError( + this, GeolocationPositionError_Binding::POSITION_UNAVAILABLE); + positionError->NotifyCallback(aErrorCallback); + return 0; + } + + if (mWatchingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return 0; + } + + // The watch ID: + int32_t watchId = mLastWatchId++; + + nsIEventTarget* target = GetMainThreadSerialEventTarget(); + RefPtr<nsGeolocationRequest> request = new nsGeolocationRequest( + this, std::move(aCallback), std::move(aErrorCallback), + std::move(aOptions), target, true, watchId); + + if (!StaticPrefs::geo_enabled() || ShouldBlockInsecureRequests() || + !request->CheckPermissionDelegate()) { + request->RequestDelayedTask(target, + nsGeolocationRequest::DelayedTaskType::Deny); + return watchId; + } + + if (!mOwner && aCallerType != CallerType::System) { + aRv.Throw(NS_ERROR_FAILURE); + return 0; + } + + if (mOwner) { + if (!RegisterRequestWithPrompt(request)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return 0; + } + + return watchId; + } + + if (aCallerType != CallerType::System) { + aRv.Throw(NS_ERROR_FAILURE); + return 0; + } + + request->Allow(JS::UndefinedHandleValue); + return watchId; +} + +void Geolocation::ClearWatch(int32_t aWatchId) { + if (aWatchId < 1) { + return; + } + + if (!mClearedWatchIDs.Contains(aWatchId)) { + mClearedWatchIDs.AppendElement(aWatchId); + } + + for (uint32_t i = 0, length = mWatchingCallbacks.Length(); i < length; ++i) { + if (mWatchingCallbacks[i]->WatchId() == aWatchId) { + mWatchingCallbacks[i]->Shutdown(); + RemoveRequest(mWatchingCallbacks[i]); + mClearedWatchIDs.RemoveElement(aWatchId); + break; + } + } + + // make sure we also search through the pending requests lists for + // watches to clear... + for (uint32_t i = 0, length = mPendingRequests.Length(); i < length; ++i) { + if (mPendingRequests[i]->IsWatch() && + (mPendingRequests[i]->WatchId() == aWatchId)) { + mPendingRequests[i]->Shutdown(); + mPendingRequests.RemoveElementAt(i); + break; + } + } +} + +bool Geolocation::WindowOwnerStillExists() { + // an owner was never set when Geolocation + // was created, which means that this object + // is being used without a window. + if (mOwner == nullptr) { + return true; + } + + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mOwner); + + if (window) { + nsPIDOMWindowOuter* outer = window->GetOuterWindow(); + if (!outer || outer->GetCurrentInnerWindow() != window || outer->Closed()) { + return false; + } + } + + return true; +} + +void Geolocation::NotifyAllowedRequest(nsGeolocationRequest* aRequest) { + if (aRequest->IsWatch()) { + mWatchingCallbacks.AppendElement(aRequest); + } else { + mPendingCallbacks.AppendElement(aRequest); + } +} + +bool Geolocation::RegisterRequestWithPrompt(nsGeolocationRequest* request) { + nsIEventTarget* target = GetMainThreadSerialEventTarget(); + ContentPermissionRequestBase::PromptResult pr = request->CheckPromptPrefs(); + if (pr == ContentPermissionRequestBase::PromptResult::Granted) { + request->RequestDelayedTask(target, + nsGeolocationRequest::DelayedTaskType::Allow); + return true; + } + if (pr == ContentPermissionRequestBase::PromptResult::Denied) { + request->RequestDelayedTask(target, + nsGeolocationRequest::DelayedTaskType::Deny); + return true; + } + + request->RequestDelayedTask(target, + nsGeolocationRequest::DelayedTaskType::Request); + return true; +} + +JSObject* Geolocation::WrapObject(JSContext* aCtx, + JS::Handle<JSObject*> aGivenProto) { + return Geolocation_Binding::Wrap(aCtx, this, aGivenProto); +} diff --git a/dom/geolocation/Geolocation.h b/dom/geolocation/Geolocation.h new file mode 100644 index 0000000000..7e1af00d05 --- /dev/null +++ b/dom/geolocation/Geolocation.h @@ -0,0 +1,249 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_Geolocation_h +#define mozilla_dom_Geolocation_h + +// Microsoft's API Name hackery sucks +#undef CreateEvent + +#include "mozilla/StaticPtr.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsITimer.h" +#include "nsIObserver.h" +#include "nsIWeakReferenceUtils.h" +#include "nsWrapperCache.h" + +#include "nsCycleCollectionParticipant.h" + +#include "GeolocationPosition.h" +#include "GeolocationCoordinates.h" +#include "nsIDOMGeoPosition.h" +#include "nsIDOMGeoPositionCallback.h" +#include "nsIDOMGeoPositionErrorCallback.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/GeolocationBinding.h" +#include "mozilla/dom/CallbackObject.h" + +#include "nsIGeolocationProvider.h" +#include "mozilla/Attributes.h" + +class nsGeolocationService; +class nsGeolocationRequest; + +namespace mozilla::dom { +class Geolocation; +using GeoPositionCallback = + CallbackObjectHolder<PositionCallback, nsIDOMGeoPositionCallback>; +using GeoPositionErrorCallback = + CallbackObjectHolder<PositionErrorCallback, nsIDOMGeoPositionErrorCallback>; +} // namespace mozilla::dom + +struct CachedPositionAndAccuracy { + nsCOMPtr<nsIDOMGeoPosition> position; + bool isHighAccuracy; +}; + +/** + * Singleton that manages the geolocation provider + */ +class nsGeolocationService final : public nsIGeolocationUpdate, + public nsIObserver { + public: + static already_AddRefed<nsGeolocationService> GetGeolocationService(); + static mozilla::StaticRefPtr<nsGeolocationService> sService; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIGEOLOCATIONUPDATE + NS_DECL_NSIOBSERVER + + nsGeolocationService() = default; + + nsresult Init(); + + // Management of the Geolocation objects + void AddLocator(mozilla::dom::Geolocation* locator); + void RemoveLocator(mozilla::dom::Geolocation* locator); + + void SetCachedPosition(nsIDOMGeoPosition* aPosition); + CachedPositionAndAccuracy GetCachedPosition(); + + // Find and startup a geolocation device (gps, nmea, etc.) + MOZ_CAN_RUN_SCRIPT nsresult StartDevice(); + + // Stop the started geolocation device (gps, nmea, etc.) + void StopDevice(); + + // create, or reinitialize the callback timer + void SetDisconnectTimer(); + + // Update the accuracy and notify the provider if changed + void UpdateAccuracy(bool aForceHigh = false); + bool HighAccuracyRequested(); + + private: + ~nsGeolocationService(); + + // Disconnect timer. When this timer expires, it clears all pending callbacks + // and closes down the provider, unless we are watching a point, and in that + // case, we disable the disconnect timer. + nsCOMPtr<nsITimer> mDisconnectTimer; + + // The object providing geo location information to us. + nsCOMPtr<nsIGeolocationProvider> mProvider; + + // mGeolocators are not owned here. Their constructor + // adds them to this list, and their destructor removes + // them from this list. + nsTArray<mozilla::dom::Geolocation*> mGeolocators; + + // This is the last geo position that we have seen. + CachedPositionAndAccuracy mLastPosition; + + // Current state of requests for higher accuracy + bool mHigherAccuracy = false; +}; + +namespace mozilla::dom { + +/** + * Can return a geolocation info + */ +class Geolocation final : public nsIGeolocationUpdate, public nsWrapperCache { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Geolocation) + + NS_DECL_NSIGEOLOCATIONUPDATE + + Geolocation(); + + nsresult Init(nsPIDOMWindowInner* aContentDom = nullptr); + + nsPIDOMWindowInner* GetParentObject() const; + virtual JSObject* WrapObject(JSContext* aCtx, + JS::Handle<JSObject*> aGivenProto) override; + + MOZ_CAN_RUN_SCRIPT + int32_t WatchPosition(PositionCallback& aCallback, + PositionErrorCallback* aErrorCallback, + const PositionOptions& aOptions, CallerType aCallerType, + ErrorResult& aRv); + + MOZ_CAN_RUN_SCRIPT + void GetCurrentPosition(PositionCallback& aCallback, + PositionErrorCallback* aErrorCallback, + const PositionOptions& aOptions, + CallerType aCallerType, ErrorResult& aRv); + void ClearWatch(int32_t aWatchId); + + // A WatchPosition for C++ use. Returns 0 if we failed to actually watch. + MOZ_CAN_RUN_SCRIPT + int32_t WatchPosition(nsIDOMGeoPositionCallback* aCallback, + nsIDOMGeoPositionErrorCallback* aErrorCallback, + UniquePtr<PositionOptions>&& aOptions); + + // Returns true if any of the callbacks are repeating + bool HasActiveCallbacks(); + + // Register an allowed request + void NotifyAllowedRequest(nsGeolocationRequest* aRequest); + + // Remove request from all callbacks arrays + void RemoveRequest(nsGeolocationRequest* request); + + // Check if there is already ClearWatch called for current + // request & clear if yes + bool ClearPendingRequest(nsGeolocationRequest* aRequest); + + // Shutting down. + void Shutdown(); + + // Getter for the principal that this Geolocation was loaded from + nsIPrincipal* GetPrincipal() { return mPrincipal; } + + // Getter for the window that this Geolocation is owned by + nsIWeakReference* GetOwner() { return mOwner; } + + // Check to see if the window still exists + bool WindowOwnerStillExists(); + + // Check to see if any active request requires high accuracy + bool HighAccuracyRequested(); + + // Get the singleton non-window Geolocation instance. This never returns + // null. + static already_AddRefed<Geolocation> NonWindowSingleton(); + + private: + ~Geolocation(); + + MOZ_CAN_RUN_SCRIPT + nsresult GetCurrentPosition(GeoPositionCallback aCallback, + GeoPositionErrorCallback aErrorCallback, + UniquePtr<PositionOptions>&& aOptions, + CallerType aCallerType); + + MOZ_CAN_RUN_SCRIPT + int32_t WatchPosition(GeoPositionCallback aCallback, + GeoPositionErrorCallback aErrorCallback, + UniquePtr<PositionOptions>&& aOptions, + CallerType aCallerType, ErrorResult& aRv); + + bool RegisterRequestWithPrompt(nsGeolocationRequest* request); + + // Check if clearWatch is already called + bool IsAlreadyCleared(nsGeolocationRequest* aRequest); + + // Returns whether the Geolocation object should block requests + // within a context that is not secure. + bool ShouldBlockInsecureRequests() const; + + // Checks if the request is in a content window that is fully active, or the + // request is coming from a chrome window. + bool IsFullyActiveOrChrome(); + + // Two callback arrays. The first |mPendingCallbacks| holds objects for only + // one callback and then they are released/removed from the array. The second + // |mWatchingCallbacks| holds objects until the object is explictly removed or + // there is a page change. All requests held by either array are active, that + // is, they have been allowed and expect to be fulfilled. + + nsTArray<RefPtr<nsGeolocationRequest> > mPendingCallbacks; + nsTArray<RefPtr<nsGeolocationRequest> > mWatchingCallbacks; + + // window that this was created for. Weak reference. + nsWeakPtr mOwner; + + // where the content was loaded from + nsCOMPtr<nsIPrincipal> mPrincipal; + + // the protocols we want to measure + enum class ProtocolType : uint8_t { OTHER, HTTP, HTTPS }; + + // the protocol used to load the content + ProtocolType mProtocolType; + + // owning back pointer. + RefPtr<nsGeolocationService> mService; + + // Watch ID + uint32_t mLastWatchId; + + // Pending requests are used when the service is not ready + nsTArray<RefPtr<nsGeolocationRequest> > mPendingRequests; + + // Array containing already cleared watch IDs + nsTArray<int32_t> mClearedWatchIDs; + + // Our cached non-window singleton. + static mozilla::StaticRefPtr<Geolocation> sNonWindowSingleton; +}; + +} // namespace mozilla::dom + +#endif /* mozilla_dom_Geolocation_h */ diff --git a/dom/geolocation/GeolocationCoordinates.cpp b/dom/geolocation/GeolocationCoordinates.cpp new file mode 100644 index 0000000000..e3dc419642 --- /dev/null +++ b/dom/geolocation/GeolocationCoordinates.cpp @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "mozilla/dom/GeolocationCoordinates.h" + +#include "mozilla/FloatingPoint.h" +#include "mozilla/dom/GeolocationPosition.h" +#include "mozilla/dom/GeolocationCoordinatesBinding.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(GeolocationCoordinates, mPosition) +NS_IMPL_CYCLE_COLLECTING_ADDREF(GeolocationCoordinates) +NS_IMPL_CYCLE_COLLECTING_RELEASE(GeolocationCoordinates) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GeolocationCoordinates) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +GeolocationCoordinates::GeolocationCoordinates(GeolocationPosition* aPosition, + nsIDOMGeoPositionCoords* aCoords) + : mPosition(aPosition), mCoords(aCoords) {} + +GeolocationCoordinates::~GeolocationCoordinates() = default; + +GeolocationPosition* GeolocationCoordinates::GetParentObject() const { + return mPosition; +} + +JSObject* GeolocationCoordinates::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return GeolocationCoordinates_Binding::Wrap(aCx, this, aGivenProto); +} + +#define GENERATE_COORDS_WRAPPED_GETTER(name) \ + double GeolocationCoordinates::name() const { \ + double rv; \ + mCoords->Get##name(&rv); \ + return rv; \ + } + +#define GENERATE_COORDS_WRAPPED_GETTER_NULLABLE(name) \ + Nullable<double> GeolocationCoordinates::Get##name() const { \ + double value; \ + mCoords->Get##name(&value); \ + Nullable<double> rv; \ + if (!std::isnan(value)) { \ + rv.SetValue(value); \ + } \ + return rv; \ + } + +GENERATE_COORDS_WRAPPED_GETTER(Latitude) +GENERATE_COORDS_WRAPPED_GETTER(Longitude) +GENERATE_COORDS_WRAPPED_GETTER_NULLABLE(Altitude) +GENERATE_COORDS_WRAPPED_GETTER(Accuracy) +GENERATE_COORDS_WRAPPED_GETTER_NULLABLE(AltitudeAccuracy) +GENERATE_COORDS_WRAPPED_GETTER_NULLABLE(Heading) +GENERATE_COORDS_WRAPPED_GETTER_NULLABLE(Speed) + +#undef GENERATE_COORDS_WRAPPED_GETTER +#undef GENERATE_COORDS_WRAPPED_GETTER_NULLABLE + +} // namespace mozilla::dom diff --git a/dom/geolocation/GeolocationCoordinates.h b/dom/geolocation/GeolocationCoordinates.h new file mode 100644 index 0000000000..28fb30b970 --- /dev/null +++ b/dom/geolocation/GeolocationCoordinates.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_GeolocationCoordinates_h +#define mozilla_dom_GeolocationCoordinates_h + +#include "nsIDOMGeoPositionCoords.h" +#include "GeolocationPosition.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "mozilla/dom/Nullable.h" +#include "js/TypeDecls.h" + +namespace mozilla::dom { + +class GeolocationCoordinates final : public nsISupports, public nsWrapperCache { + ~GeolocationCoordinates(); + + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(GeolocationCoordinates) + + public: + GeolocationCoordinates(GeolocationPosition* aPosition, + nsIDOMGeoPositionCoords* aCoords); + + GeolocationPosition* GetParentObject() const; + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + double Latitude() const; + + double Longitude() const; + + Nullable<double> GetAltitude() const; + + double Accuracy() const; + + Nullable<double> GetAltitudeAccuracy() const; + + Nullable<double> GetHeading() const; + + Nullable<double> GetSpeed() const; + + private: + RefPtr<GeolocationPosition> mPosition; + nsCOMPtr<nsIDOMGeoPositionCoords> mCoords; +}; + +} // namespace mozilla::dom + +#endif /* mozilla_dom_GeolocationCoordinates_h */ diff --git a/dom/geolocation/GeolocationPosition.cpp b/dom/geolocation/GeolocationPosition.cpp new file mode 100644 index 0000000000..19ca685251 --- /dev/null +++ b/dom/geolocation/GeolocationPosition.cpp @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "mozilla/dom/GeolocationPosition.h" +#include "mozilla/dom/GeolocationCoordinates.h" + +#include "mozilla/FloatingPoint.h" +#include "mozilla/dom/GeolocationPositionBinding.h" + +using mozilla::EqualOrBothNaN; + +// NaN() is a more convenient function name. +inline double NaN() { return mozilla::UnspecifiedNaN<double>(); } + +//////////////////////////////////////////////////// +// nsGeoPositionCoords +//////////////////////////////////////////////////// +nsGeoPositionCoords::nsGeoPositionCoords(double aLat, double aLong, double aAlt, + double aHError, double aVError, + double aHeading, double aSpeed) + : mLat(aLat), + mLong(aLong), + mAlt(aAlt), + mHError((aHError >= 0) ? aHError : 0) + // altitudeAccuracy without an altitude doesn't make any sense. + , + mVError((aVError >= 0 && !std::isnan(aAlt)) ? aVError : NaN()) + // If the hosting device is stationary (i.e. the value of the speed + // attribute is 0), then the value of the heading attribute must be NaN + // (or null). + , + mHeading((aHeading >= 0 && aHeading < 360 && aSpeed > 0) ? aHeading + : NaN()), + mSpeed(aSpeed >= 0 ? aSpeed : NaN()) { + // Sanity check the location provider's results in debug builds. If the + // location provider is returning bogus results, we'd like to know, but + // we prefer to return some position data to JavaScript over a + // POSITION_UNAVAILABLE error. + MOZ_ASSERT(aLat >= -90 && aLat <= 90); + MOZ_ASSERT(aLong >= -180 && aLong <= 180); + MOZ_ASSERT(!(aLat == 0 && aLong == 0)); // valid but probably a bug + + MOZ_ASSERT(EqualOrBothNaN(mAlt, aAlt)); + MOZ_ASSERT(mHError == aHError); + MOZ_ASSERT(EqualOrBothNaN(mVError, aVError)); + MOZ_ASSERT(EqualOrBothNaN(mHeading, aHeading)); + MOZ_ASSERT(EqualOrBothNaN(mSpeed, aSpeed)); +} + +nsGeoPositionCoords::~nsGeoPositionCoords() = default; + +NS_INTERFACE_MAP_BEGIN(nsGeoPositionCoords) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMGeoPositionCoords) + NS_INTERFACE_MAP_ENTRY(nsIDOMGeoPositionCoords) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsGeoPositionCoords) +NS_IMPL_RELEASE(nsGeoPositionCoords) + +NS_IMETHODIMP +nsGeoPositionCoords::GetLatitude(double* aLatitude) { + *aLatitude = mLat; + return NS_OK; +} + +NS_IMETHODIMP +nsGeoPositionCoords::GetLongitude(double* aLongitude) { + *aLongitude = mLong; + return NS_OK; +} + +NS_IMETHODIMP +nsGeoPositionCoords::GetAltitude(double* aAltitude) { + *aAltitude = mAlt; + return NS_OK; +} + +NS_IMETHODIMP +nsGeoPositionCoords::GetAccuracy(double* aAccuracy) { + *aAccuracy = mHError; + return NS_OK; +} + +NS_IMETHODIMP +nsGeoPositionCoords::GetAltitudeAccuracy(double* aAltitudeAccuracy) { + *aAltitudeAccuracy = mVError; + return NS_OK; +} + +NS_IMETHODIMP +nsGeoPositionCoords::GetHeading(double* aHeading) { + *aHeading = mHeading; + return NS_OK; +} + +NS_IMETHODIMP +nsGeoPositionCoords::GetSpeed(double* aSpeed) { + *aSpeed = mSpeed; + return NS_OK; +} + +//////////////////////////////////////////////////// +// nsGeoPosition +//////////////////////////////////////////////////// + +nsGeoPosition::nsGeoPosition(double aLat, double aLong, double aAlt, + double aHError, double aVError, double aHeading, + double aSpeed, EpochTimeStamp aTimestamp) + : mTimestamp(aTimestamp) { + mCoords = new nsGeoPositionCoords(aLat, aLong, aAlt, aHError, aVError, + aHeading, aSpeed); +} + +nsGeoPosition::nsGeoPosition(nsIDOMGeoPositionCoords* aCoords, + EpochTimeStamp aTimestamp) + : mTimestamp(aTimestamp), mCoords(aCoords) {} + +nsGeoPosition::~nsGeoPosition() = default; + +NS_INTERFACE_MAP_BEGIN(nsGeoPosition) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMGeoPosition) + NS_INTERFACE_MAP_ENTRY(nsIDOMGeoPosition) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsGeoPosition) +NS_IMPL_RELEASE(nsGeoPosition) + +NS_IMETHODIMP +nsGeoPosition::GetTimestamp(EpochTimeStamp* aTimestamp) { + *aTimestamp = mTimestamp; + return NS_OK; +} + +NS_IMETHODIMP +nsGeoPosition::GetCoords(nsIDOMGeoPositionCoords** aCoords) { + NS_IF_ADDREF(*aCoords = mCoords); + return NS_OK; +} + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(GeolocationPosition, mParent, + mCoordinates) +NS_IMPL_CYCLE_COLLECTING_ADDREF(GeolocationPosition) +NS_IMPL_CYCLE_COLLECTING_RELEASE(GeolocationPosition) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GeolocationPosition) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +GeolocationPosition::GeolocationPosition(nsISupports* aParent, + nsIDOMGeoPosition* aGeoPosition) + : mParent(aParent), mGeoPosition(aGeoPosition) {} + +GeolocationPosition::~GeolocationPosition() = default; + +nsISupports* GeolocationPosition::GetParentObject() const { return mParent; } + +JSObject* GeolocationPosition::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return GeolocationPosition_Binding::Wrap(aCx, this, aGivenProto); +} + +GeolocationCoordinates* GeolocationPosition::Coords() { + if (!mCoordinates) { + nsCOMPtr<nsIDOMGeoPositionCoords> coords; + mGeoPosition->GetCoords(getter_AddRefs(coords)); + MOZ_ASSERT(coords, "coords should not be null"); + + mCoordinates = new GeolocationCoordinates(this, coords); + } + + return mCoordinates; +} + +uint64_t GeolocationPosition::Timestamp() const { + uint64_t rv; + + mGeoPosition->GetTimestamp(&rv); + return rv; +} + +} // namespace mozilla::dom diff --git a/dom/geolocation/GeolocationPosition.h b/dom/geolocation/GeolocationPosition.h new file mode 100644 index 0000000000..87b883a69e --- /dev/null +++ b/dom/geolocation/GeolocationPosition.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_GeolocationPosition_h +#define mozilla_dom_GeolocationPosition_h + +#include "nsIDOMGeoPositionCoords.h" +#include "nsIDOMGeoPosition.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "js/TypeDecls.h" + +//////////////////////////////////////////////////// +// nsGeoPositionCoords +//////////////////////////////////////////////////// + +/** + * Simple object that holds a single point in space. + */ +class nsGeoPositionCoords final : public nsIDOMGeoPositionCoords { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDOMGEOPOSITIONCOORDS + + nsGeoPositionCoords(double aLat, double aLong, double aAlt, double aHError, + double aVError, double aHeading, double aSpeed); + + private: + ~nsGeoPositionCoords(); + const double mLat, mLong, mAlt, mHError, mVError, mHeading, mSpeed; +}; + +//////////////////////////////////////////////////// +// nsGeoPosition +//////////////////////////////////////////////////// + +class nsGeoPosition final : public nsIDOMGeoPosition { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDOMGEOPOSITION + + nsGeoPosition(double aLat, double aLong, double aAlt, double aHError, + double aVError, double aHeading, double aSpeed, + EpochTimeStamp aTimestamp); + + nsGeoPosition(nsIDOMGeoPositionCoords* aCoords, EpochTimeStamp aTimestamp); + + private: + ~nsGeoPosition(); + EpochTimeStamp mTimestamp; + RefPtr<nsIDOMGeoPositionCoords> mCoords; +}; + +//////////////////////////////////////////////////// +// WebIDL wrappers for the classes above +//////////////////////////////////////////////////// + +namespace mozilla::dom { + +class GeolocationCoordinates; + +class GeolocationPosition final : public nsISupports, public nsWrapperCache { + ~GeolocationPosition(); + + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(GeolocationPosition) + + public: + GeolocationPosition(nsISupports* aParent, nsIDOMGeoPosition* aGeoPosition); + + nsISupports* GetParentObject() const; + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + GeolocationCoordinates* Coords(); + + uint64_t Timestamp() const; + + nsIDOMGeoPosition* GetWrappedGeoPosition() { return mGeoPosition; } + + private: + RefPtr<GeolocationCoordinates> mCoordinates; + nsCOMPtr<nsISupports> mParent; + nsCOMPtr<nsIDOMGeoPosition> mGeoPosition; +}; + +} // namespace mozilla::dom + +#endif /* mozilla_dom_GeolocationPosition_h */ diff --git a/dom/geolocation/GeolocationPositionError.cpp b/dom/geolocation/GeolocationPositionError.cpp new file mode 100644 index 0000000000..1fb6274a6b --- /dev/null +++ b/dom/geolocation/GeolocationPositionError.cpp @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "mozilla/dom/GeolocationPositionError.h" +#include "mozilla/dom/GeolocationPositionErrorBinding.h" +#include "mozilla/CycleCollectedJSContext.h" // for nsAutoMicroTask +#include "Geolocation.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(GeolocationPositionError, mParent) + +GeolocationPositionError::GeolocationPositionError(Geolocation* aParent, + int16_t aCode) + : mCode(aCode), mParent(aParent) {} + +GeolocationPositionError::~GeolocationPositionError() = default; + +void GeolocationPositionError::GetMessage(nsAString& aMessage) const { + switch (mCode) { + case GeolocationPositionError_Binding::PERMISSION_DENIED: + aMessage = u"User denied geolocation prompt"_ns; + break; + case GeolocationPositionError_Binding::POSITION_UNAVAILABLE: + aMessage = u"Unknown error acquiring position"_ns; + break; + case GeolocationPositionError_Binding::TIMEOUT: + aMessage = u"Position acquisition timed out"_ns; + break; + default: + break; + } +} + +nsWrapperCache* GeolocationPositionError::GetParentObject() const { + return mParent; +} + +JSObject* GeolocationPositionError::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return GeolocationPositionError_Binding::Wrap(aCx, this, aGivenProto); +} + +void GeolocationPositionError::NotifyCallback( + const GeoPositionErrorCallback& aCallback) { + nsAutoMicroTask mt; + if (aCallback.HasWebIDLCallback()) { + RefPtr<PositionErrorCallback> callback = aCallback.GetWebIDLCallback(); + + if (callback) { + callback->Call(*this); + } + } else { + nsIDOMGeoPositionErrorCallback* callback = aCallback.GetXPCOMCallback(); + if (callback) { + callback->HandleEvent(this); + } + } +} + +} // namespace mozilla::dom diff --git a/dom/geolocation/GeolocationPositionError.h b/dom/geolocation/GeolocationPositionError.h new file mode 100644 index 0000000000..c379149791 --- /dev/null +++ b/dom/geolocation/GeolocationPositionError.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_GeolocationPositionError_h +#define mozilla_dom_GeolocationPositionError_h + +#include "nsWrapperCache.h" +#include "nsISupportsImpl.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/dom/CallbackObject.h" + +class nsIDOMGeoPositionErrorCallback; + +namespace mozilla::dom { +class PositionErrorCallback; +class Geolocation; +typedef CallbackObjectHolder<PositionErrorCallback, + nsIDOMGeoPositionErrorCallback> + GeoPositionErrorCallback; + +class GeolocationPositionError final : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(GeolocationPositionError) + NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(GeolocationPositionError) + + GeolocationPositionError(Geolocation* aParent, int16_t aCode); + + nsWrapperCache* GetParentObject() const; + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + int16_t Code() const { return mCode; } + + void GetMessage(nsAString& aMessage) const; + + MOZ_CAN_RUN_SCRIPT + void NotifyCallback(const GeoPositionErrorCallback& callback); + + private: + ~GeolocationPositionError(); + int16_t mCode; + RefPtr<Geolocation> mParent; +}; + +} // namespace mozilla::dom + +#endif /* mozilla_dom_GeolocationPositionError_h */ diff --git a/dom/geolocation/MLSFallback.cpp b/dom/geolocation/MLSFallback.cpp new file mode 100644 index 0000000000..82398a4436 --- /dev/null +++ b/dom/geolocation/MLSFallback.cpp @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "MLSFallback.h" +#include "GeolocationPosition.h" +#include "nsComponentManagerUtils.h" +#include "nsIGeolocationProvider.h" +#include "nsServiceManagerUtils.h" + +NS_IMPL_ISUPPORTS(MLSFallback, nsITimerCallback, nsINamed) + +MLSFallback::MLSFallback(uint32_t delay) : mDelayMs(delay) {} + +MLSFallback::~MLSFallback() = default; + +nsresult MLSFallback::Startup(nsIGeolocationUpdate* aWatcher) { + if (mHandoffTimer || mMLSFallbackProvider) { + return NS_OK; + } + + mUpdateWatcher = aWatcher; + + // No need to schedule a timer callback if there is no startup delay. + if (mDelayMs == 0) { + return CreateMLSFallbackProvider(); + } + + return NS_NewTimerWithCallback(getter_AddRefs(mHandoffTimer), this, mDelayMs, + nsITimer::TYPE_ONE_SHOT); +} + +nsresult MLSFallback::Shutdown() { + mUpdateWatcher = nullptr; + + if (mHandoffTimer) { + mHandoffTimer->Cancel(); + mHandoffTimer = nullptr; + } + + nsresult rv = NS_OK; + if (mMLSFallbackProvider) { + rv = mMLSFallbackProvider->Shutdown(); + mMLSFallbackProvider = nullptr; + } + return rv; +} + +NS_IMETHODIMP +MLSFallback::Notify(nsITimer* aTimer) { return CreateMLSFallbackProvider(); } + +NS_IMETHODIMP +MLSFallback::GetName(nsACString& aName) { + aName.AssignLiteral("MLSFallback"); + return NS_OK; +} + +nsresult MLSFallback::CreateMLSFallbackProvider() { + if (mMLSFallbackProvider || !mUpdateWatcher) { + return NS_OK; + } + + nsresult rv; + mMLSFallbackProvider = + do_CreateInstance("@mozilla.org/geolocation/mls-provider;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (mMLSFallbackProvider) { + rv = mMLSFallbackProvider->Startup(); + if (NS_SUCCEEDED(rv)) { + mMLSFallbackProvider->Watch(mUpdateWatcher); + } + } + mUpdateWatcher = nullptr; + return rv; +} diff --git a/dom/geolocation/MLSFallback.h b/dom/geolocation/MLSFallback.h new file mode 100644 index 0000000000..f0e7f75519 --- /dev/null +++ b/dom/geolocation/MLSFallback.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "nsCOMPtr.h" +#include "nsITimer.h" +#include "nsINamed.h" + +class nsIGeolocationUpdate; +class nsIGeolocationProvider; + +/* + This class wraps the NetworkGeolocationProvider in a delayed startup. + It is for providing a fallback to MLS when: + 1) using another provider as the primary provider, and + 2) that primary provider may fail to return a result (i.e. the error returned + is indeterminate, or no error callback occurs) + + The intent is that the primary provider is started, then MLSFallback + is started with sufficient delay that the primary provider will respond first + if successful (in the majority of cases). + + MLS has an average response of 3s, so with the 2s default delay, a response can + be expected in 5s. + + Telemetry is recommended to monitor that the primary provider is responding + first when expected to do so. +*/ +class MLSFallback : public nsITimerCallback, public nsINamed { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + explicit MLSFallback(uint32_t delayMs = 2000); + nsresult Startup(nsIGeolocationUpdate* aWatcher); + nsresult Shutdown(); + + private: + nsresult CreateMLSFallbackProvider(); + virtual ~MLSFallback(); + nsCOMPtr<nsITimer> mHandoffTimer; + nsCOMPtr<nsIGeolocationProvider> mMLSFallbackProvider; + nsCOMPtr<nsIGeolocationUpdate> mUpdateWatcher; + const uint32_t mDelayMs; +}; diff --git a/dom/geolocation/moz.build b/dom/geolocation/moz.build new file mode 100644 index 0000000000..2d6b6b5fab --- /dev/null +++ b/dom/geolocation/moz.build @@ -0,0 +1,60 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("Core", "DOM: Geolocation") + +EXPORTS += [ + "nsGeoPositionIPCSerialiser.h", +] + +EXPORTS.mozilla.dom += [ + "Geolocation.h", + "GeolocationCoordinates.h", + "GeolocationPosition.h", + "GeolocationPositionError.h", +] + +SOURCES += [ + "Geolocation.cpp", + "GeolocationCoordinates.cpp", + "GeolocationPosition.cpp", + "GeolocationPositionError.cpp", +] + +UNIFIED_SOURCES += [ + "MLSFallback.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" +LOCAL_INCLUDES += [ + "/dom/base", + "/dom/ipc", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": + LOCAL_INCLUDES += [ + "/dom/system/android", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + LOCAL_INCLUDES += [ + "/dom/system/mac", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + LOCAL_INCLUDES += [ + "/dom/system/windows/location", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + if CONFIG["MOZ_GPSD"]: + LOCAL_INCLUDES += [ + "/dom/system/linux", + ] + DEFINES["MOZ_GPSD"] = True + if CONFIG["MOZ_ENABLE_DBUS"]: + LOCAL_INCLUDES += ["/dom/system/linux"] + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] diff --git a/dom/geolocation/nsGeoPositionIPCSerialiser.h b/dom/geolocation/nsGeoPositionIPCSerialiser.h new file mode 100644 index 0000000000..c1a255ad2b --- /dev/null +++ b/dom/geolocation/nsGeoPositionIPCSerialiser.h @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef dom_src_geolocation_IPC_serialiser +#define dom_src_geolocation_IPC_serialiser + +#include "ipc/IPCMessageUtils.h" +#include "mozilla/dom/GeolocationPosition.h" +#include "nsIDOMGeoPosition.h" + +namespace IPC { + +template <> +struct ParamTraits<nsIDOMGeoPositionCoords*> { + // Function to serialize a geoposition + static void Write(MessageWriter* aWriter, nsIDOMGeoPositionCoords* aParam) { + bool isNull = !aParam; + WriteParam(aWriter, isNull); + // If it is a null object, then we are done + if (isNull) return; + + double coordData; + + aParam->GetLatitude(&coordData); + WriteParam(aWriter, coordData); + + aParam->GetLongitude(&coordData); + WriteParam(aWriter, coordData); + + aParam->GetAltitude(&coordData); + WriteParam(aWriter, coordData); + + aParam->GetAccuracy(&coordData); + WriteParam(aWriter, coordData); + + aParam->GetAltitudeAccuracy(&coordData); + WriteParam(aWriter, coordData); + + aParam->GetHeading(&coordData); + WriteParam(aWriter, coordData); + + aParam->GetSpeed(&coordData); + WriteParam(aWriter, coordData); + } + + // Function to de-serialize a geoposition + static bool Read(MessageReader* aReader, + RefPtr<nsIDOMGeoPositionCoords>* aResult) { + // Check if it is the null pointer we have transfered + bool isNull; + if (!ReadParam(aReader, &isNull)) return false; + + if (isNull) { + *aResult = nullptr; + return true; + } + + double latitude; + double longitude; + double altitude; + double accuracy; + double altitudeAccuracy; + double heading; + double speed; + + // It's not important to us where it fails, but rather if it fails + if (!(ReadParam(aReader, &latitude) && ReadParam(aReader, &longitude) && + ReadParam(aReader, &altitude) && ReadParam(aReader, &accuracy) && + ReadParam(aReader, &altitudeAccuracy) && + ReadParam(aReader, &heading) && ReadParam(aReader, &speed))) + return false; + + // We now have all the data + *aResult = new nsGeoPositionCoords(latitude, /* aLat */ + longitude, /* aLong */ + altitude, /* aAlt */ + accuracy, /* aHError */ + altitudeAccuracy, /* aVError */ + heading, /* aHeading */ + speed /* aSpeed */ + ); + return true; + } +}; + +template <> +struct ParamTraits<nsIDOMGeoPosition*> { + // Function to serialize a geoposition + static void Write(MessageWriter* aWriter, nsIDOMGeoPosition* aParam) { + bool isNull = !aParam; + WriteParam(aWriter, isNull); + // If it is a null object, then we are done + if (isNull) return; + + EpochTimeStamp timeStamp; + aParam->GetTimestamp(&timeStamp); + WriteParam(aWriter, timeStamp); + + nsCOMPtr<nsIDOMGeoPositionCoords> coords; + aParam->GetCoords(getter_AddRefs(coords)); + WriteParam(aWriter, coords); + } + + // Function to de-serialize a geoposition + static bool Read(MessageReader* aReader, RefPtr<nsIDOMGeoPosition>* aResult) { + // Check if it is the null pointer we have transfered + bool isNull; + if (!ReadParam(aReader, &isNull)) return false; + + if (isNull) { + *aResult = nullptr; + return true; + } + + EpochTimeStamp timeStamp; + RefPtr<nsIDOMGeoPositionCoords> coords; + + // It's not important to us where it fails, but rather if it fails + if (!ReadParam(aReader, &timeStamp) || !ReadParam(aReader, &coords)) { + return false; + } + + *aResult = new nsGeoPosition(coords, timeStamp); + + return true; + }; +}; + +} // namespace IPC + +#endif |