summaryrefslogtreecommitdiffstats
path: root/dom/geolocation
diff options
context:
space:
mode:
Diffstat (limited to 'dom/geolocation')
-rw-r--r--dom/geolocation/Geolocation.cpp1221
-rw-r--r--dom/geolocation/Geolocation.h249
-rw-r--r--dom/geolocation/GeolocationCoordinates.cpp67
-rw-r--r--dom/geolocation/GeolocationCoordinates.h56
-rw-r--r--dom/geolocation/GeolocationPosition.cpp186
-rw-r--r--dom/geolocation/GeolocationPosition.h95
-rw-r--r--dom/geolocation/GeolocationPositionError.cpp64
-rw-r--r--dom/geolocation/GeolocationPositionError.h51
-rw-r--r--dom/geolocation/MLSFallback.cpp78
-rw-r--r--dom/geolocation/MLSFallback.h48
-rw-r--r--dom/geolocation/moz.build60
-rw-r--r--dom/geolocation/nsGeoPositionIPCSerialiser.h134
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