1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "Geolocation.h"
9 #include "mozilla/ClearOnShutdown.h"
10 #include "mozilla/CycleCollectedJSContext.h" // for nsAutoMicroTask
11 #include "mozilla/dom/ContentChild.h"
12 #include "mozilla/dom/PermissionMessageUtils.h"
13 #include "mozilla/dom/GeolocationPositionError.h"
14 #include "mozilla/dom/GeolocationPositionErrorBinding.h"
15 #include "mozilla/glean/GleanMetrics.h"
16 #include "mozilla/Preferences.h"
17 #include "mozilla/Services.h"
18 #include "mozilla/StaticPrefs_geo.h"
19 #include "mozilla/Telemetry.h"
20 #include "mozilla/UniquePtr.h"
21 #include "mozilla/Unused.h"
22 #include "mozilla/WeakPtr.h"
23 #include "mozilla/EventStateManager.h"
24 #include "nsComponentManagerUtils.h"
25 #include "nsContentPermissionHelper.h"
26 #include "nsContentUtils.h"
27 #include "nsGlobalWindowInner.h"
28 #include "mozilla/dom/Document.h"
30 #include "nsIObserverService.h"
31 #include "nsIScriptError.h"
32 #include "nsPIDOMWindow.h"
33 #include "nsServiceManagerUtils.h"
34 #include "nsThreadUtils.h"
35 #include "nsXULAppAPI.h"
39 #ifdef MOZ_WIDGET_ANDROID
40 # include "AndroidLocationProvider.h"
44 # include "GpsdLocationProvider.h"
47 #ifdef MOZ_ENABLE_DBUS
48 # include "mozilla/WidgetUtilsGtk.h"
49 # include "GeoclueLocationProvider.h"
50 # include "PortalLocationProvider.h"
53 #ifdef MOZ_WIDGET_COCOA
54 # include "CoreLocationLocationProvider.h"
58 # include "WindowsLocationProvider.h"
61 // Some limit to the number of get or watch geolocation requests
62 // that a window can make.
63 #define MAX_GEO_REQUESTS_PER_WINDOW 1500
65 // This preference allows to override the "secure context" by
67 #define PREF_GEO_SECURITY_ALLOWINSECURE "geo.security.allowinsecure"
69 using mozilla::Unused
; // <snicker>
70 using namespace mozilla
;
71 using namespace mozilla::dom
;
73 mozilla::LazyLogModule
gGeolocationLog("Geolocation");
75 class nsGeolocationRequest final
: public ContentPermissionRequestBase
,
76 public nsIGeolocationUpdate
,
77 public SupportsWeakPtr
{
79 NS_DECL_ISUPPORTS_INHERITED
80 NS_DECL_NSIGEOLOCATIONUPDATE
82 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsGeolocationRequest
,
83 ContentPermissionRequestBase
)
85 nsGeolocationRequest(Geolocation
* aLocator
, GeoPositionCallback aCallback
,
86 GeoPositionErrorCallback aErrorCallback
,
87 UniquePtr
<PositionOptions
>&& aOptions
,
88 nsIEventTarget
* aMainThreadSerialEventTarget
,
89 bool aWatchPositionRequest
= false,
90 int32_t aWatchId
= 0);
92 // nsIContentPermissionRequest
93 MOZ_CAN_RUN_SCRIPT NS_IMETHOD
Cancel(void) override
;
94 MOZ_CAN_RUN_SCRIPT NS_IMETHOD
Allow(JS::Handle
<JS::Value
> choices
) override
;
98 // MOZ_CAN_RUN_SCRIPT_BOUNDARY is OK here because we're always called from a
99 // runnable. Ideally nsIRunnable::Run and its overloads would just be
100 // MOZ_CAN_RUN_SCRIPT and then we could be too...
101 MOZ_CAN_RUN_SCRIPT_BOUNDARY
102 void SendLocation(nsIDOMGeoPosition
* aLocation
);
103 bool WantsHighAccuracy() {
104 return !mShutdown
&& mOptions
&& mOptions
->mEnableHighAccuracy
;
106 void SetTimeoutTimer();
107 void StopTimeoutTimer();
109 void NotifyErrorAndShutdown(uint16_t);
110 using ContentPermissionRequestBase::GetPrincipal
;
111 nsIPrincipal
* GetPrincipal();
113 bool IsWatch() { return mIsWatchPositionRequest
; }
114 int32_t WatchId() { return mWatchId
; }
117 virtual ~nsGeolocationRequest();
119 class TimerCallbackHolder final
: public nsITimerCallback
, public nsINamed
{
122 NS_DECL_NSITIMERCALLBACK
124 explicit TimerCallbackHolder(nsGeolocationRequest
* aRequest
)
125 : mRequest(aRequest
) {}
127 NS_IMETHOD
GetName(nsACString
& aName
) override
{
128 aName
.AssignLiteral("nsGeolocationRequest::TimerCallbackHolder");
133 ~TimerCallbackHolder() = default;
134 WeakPtr
<nsGeolocationRequest
> mRequest
;
137 // Only called from a timer, so MOZ_CAN_RUN_SCRIPT_BOUNDARY ok for now.
138 MOZ_CAN_RUN_SCRIPT_BOUNDARY
void Notify();
140 bool mIsWatchPositionRequest
;
142 nsCOMPtr
<nsITimer
> mTimeoutTimer
;
143 GeoPositionCallback mCallback
;
144 GeoPositionErrorCallback mErrorCallback
;
145 UniquePtr
<PositionOptions
> mOptions
;
147 RefPtr
<Geolocation
> mLocator
;
151 bool mSeenAnySignal
= false;
152 nsCOMPtr
<nsIEventTarget
> mMainThreadSerialEventTarget
;
155 static UniquePtr
<PositionOptions
> CreatePositionOptionsCopy(
156 const PositionOptions
& aOptions
) {
157 UniquePtr
<PositionOptions
> geoOptions
= MakeUnique
<PositionOptions
>();
159 geoOptions
->mEnableHighAccuracy
= aOptions
.mEnableHighAccuracy
;
160 geoOptions
->mMaximumAge
= aOptions
.mMaximumAge
;
161 geoOptions
->mTimeout
= aOptions
.mTimeout
;
166 class RequestSendLocationEvent
: public Runnable
{
168 RequestSendLocationEvent(nsIDOMGeoPosition
* aPosition
,
169 nsGeolocationRequest
* aRequest
)
170 : mozilla::Runnable("RequestSendLocationEvent"),
171 mPosition(aPosition
),
172 mRequest(aRequest
) {}
174 NS_IMETHOD
Run() override
{
175 mRequest
->SendLocation(mPosition
);
180 nsCOMPtr
<nsIDOMGeoPosition
> mPosition
;
181 RefPtr
<nsGeolocationRequest
> mRequest
;
182 RefPtr
<Geolocation
> mLocator
;
185 ////////////////////////////////////////////////////
186 // nsGeolocationRequest
187 ////////////////////////////////////////////////////
189 static nsPIDOMWindowInner
* ConvertWeakReferenceToWindow(
190 nsIWeakReference
* aWeakPtr
) {
191 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryReferent(aWeakPtr
);
192 // This isn't usually safe, but here we're just extracting a raw pointer in
193 // order to pass it to a base class constructor which will in turn convert it
194 // into a strong pointer for us.
195 nsPIDOMWindowInner
* raw
= window
.get();
199 nsGeolocationRequest::nsGeolocationRequest(
200 Geolocation
* aLocator
, GeoPositionCallback aCallback
,
201 GeoPositionErrorCallback aErrorCallback
,
202 UniquePtr
<PositionOptions
>&& aOptions
,
203 nsIEventTarget
* aMainThreadSerialEventTarget
, bool aWatchPositionRequest
,
205 : ContentPermissionRequestBase(
206 aLocator
->GetPrincipal(),
207 ConvertWeakReferenceToWindow(aLocator
->GetOwner()), "geo"_ns
,
209 mIsWatchPositionRequest(aWatchPositionRequest
),
210 mCallback(std::move(aCallback
)),
211 mErrorCallback(std::move(aErrorCallback
)),
212 mOptions(std::move(aOptions
)),
216 mMainThreadSerialEventTarget(aMainThreadSerialEventTarget
) {}
218 nsGeolocationRequest::~nsGeolocationRequest() { StopTimeoutTimer(); }
220 NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(nsGeolocationRequest
,
221 ContentPermissionRequestBase
,
222 nsIGeolocationUpdate
)
224 NS_IMPL_ADDREF_INHERITED(nsGeolocationRequest
, ContentPermissionRequestBase
)
225 NS_IMPL_RELEASE_INHERITED(nsGeolocationRequest
, ContentPermissionRequestBase
)
226 NS_IMPL_CYCLE_COLLECTION_WEAK_PTR_INHERITED(nsGeolocationRequest
,
227 ContentPermissionRequestBase
,
228 mCallback
, mErrorCallback
, mLocator
)
230 void nsGeolocationRequest::Notify() {
231 MOZ_LOG(gGeolocationLog
, LogLevel::Debug
, ("nsGeolocationRequest::Notify"));
233 NotifyErrorAndShutdown(GeolocationPositionError_Binding::TIMEOUT
);
236 void nsGeolocationRequest::NotifyErrorAndShutdown(uint16_t aErrorCode
) {
237 MOZ_LOG(gGeolocationLog
, LogLevel::Debug
,
238 ("nsGeolocationRequest::NotifyErrorAndShutdown with error code: %u",
240 MOZ_ASSERT(!mShutdown
, "timeout after shutdown");
241 if (!mIsWatchPositionRequest
) {
243 mLocator
->RemoveRequest(this);
246 NotifyError(aErrorCode
);
250 nsGeolocationRequest::Cancel() {
251 if (mLocator
->ClearPendingRequest(this)) {
255 NotifyError(GeolocationPositionError_Binding::PERMISSION_DENIED
);
260 nsGeolocationRequest::Allow(JS::Handle
<JS::Value
> aChoices
) {
261 MOZ_ASSERT(aChoices
.isUndefined());
263 if (mLocator
->ClearPendingRequest(this)) {
267 RefPtr
<nsGeolocationService
> gs
=
268 nsGeolocationService::GetGeolocationService();
270 bool canUseCache
= false;
271 CachedPositionAndAccuracy lastPosition
= gs
->GetCachedPosition();
272 if (lastPosition
.position
) {
273 EpochTimeStamp cachedPositionTime_ms
;
274 lastPosition
.position
->GetTimestamp(&cachedPositionTime_ms
);
275 // check to see if we can use a cached value
276 // if the user has specified a maximumAge, return a cached value.
277 if (mOptions
&& mOptions
->mMaximumAge
> 0) {
278 uint32_t maximumAge_ms
= mOptions
->mMaximumAge
;
279 bool isCachedWithinRequestedAccuracy
=
280 WantsHighAccuracy() <= lastPosition
.isHighAccuracy
;
281 bool isCachedWithinRequestedTime
=
282 EpochTimeStamp(PR_Now() / PR_USEC_PER_MSEC
- maximumAge_ms
) <=
283 cachedPositionTime_ms
;
285 isCachedWithinRequestedAccuracy
&& isCachedWithinRequestedTime
;
289 gs
->UpdateAccuracy(WantsHighAccuracy());
291 // okay, we can return a cached position
292 // getCurrentPosition requests serviced by the cache
293 // will now be owned by the RequestSendLocationEvent
294 Update(lastPosition
.position
);
296 // After Update is called, getCurrentPosition finishes it's job.
297 if (!mIsWatchPositionRequest
) {
302 // if it is not a watch request and timeout is 0,
303 // invoke the errorCallback (if present) with TIMEOUT code
304 if (mOptions
&& mOptions
->mTimeout
== 0 && !mIsWatchPositionRequest
) {
305 NotifyError(GeolocationPositionError_Binding::TIMEOUT
);
310 // Non-cached location request
311 bool allowedRequest
= mIsWatchPositionRequest
|| !canUseCache
;
312 if (allowedRequest
) {
313 // let the locator know we're pending
314 // we will now be owned by the locator
315 mLocator
->NotifyAllowedRequest(this);
318 // Kick off the geo device, if it isn't already running
319 nsresult rv
= gs
->StartDevice();
322 if (allowedRequest
) {
323 mLocator
->RemoveRequest(this);
325 // Location provider error
326 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE
);
335 void nsGeolocationRequest::SetTimeoutTimer() {
336 MOZ_ASSERT(!mShutdown
, "set timeout after shutdown");
340 if (mOptions
&& mOptions
->mTimeout
!= 0 && mOptions
->mTimeout
!= 0x7fffffff) {
341 RefPtr
<TimerCallbackHolder
> holder
= new TimerCallbackHolder(this);
342 NS_NewTimerWithCallback(getter_AddRefs(mTimeoutTimer
), holder
,
343 mOptions
->mTimeout
, nsITimer::TYPE_ONE_SHOT
);
347 void nsGeolocationRequest::StopTimeoutTimer() {
349 mTimeoutTimer
->Cancel();
350 mTimeoutTimer
= nullptr;
354 void nsGeolocationRequest::SendLocation(nsIDOMGeoPosition
* aPosition
) {
356 // Ignore SendLocationEvents issued before we were cleared.
360 if (mOptions
&& mOptions
->mMaximumAge
> 0) {
361 EpochTimeStamp positionTime_ms
;
362 aPosition
->GetTimestamp(&positionTime_ms
);
363 const uint32_t maximumAge_ms
= mOptions
->mMaximumAge
;
364 const bool isTooOld
= EpochTimeStamp(PR_Now() / PR_USEC_PER_MSEC
-
365 maximumAge_ms
) > positionTime_ms
;
371 RefPtr
<mozilla::dom::GeolocationPosition
> wrapped
;
374 nsCOMPtr
<nsIDOMGeoPositionCoords
> coords
;
375 aPosition
->GetCoords(getter_AddRefs(coords
));
377 wrapped
= new mozilla::dom::GeolocationPosition(ToSupports(mLocator
),
383 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE
);
387 if (!mIsWatchPositionRequest
) {
388 // Cancel timer and position updates in case the position
389 // callback spins the event loop
393 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
394 obs
->NotifyObservers(wrapped
, "geolocation-position-events",
395 u
"location-updated");
398 if (mCallback
.HasWebIDLCallback()) {
399 RefPtr
<PositionCallback
> callback
= mCallback
.GetWebIDLCallback();
401 MOZ_ASSERT(callback
);
402 callback
->Call(*wrapped
);
404 nsIDOMGeoPositionCallback
* callback
= mCallback
.GetXPCOMCallback();
405 MOZ_ASSERT(callback
);
406 callback
->HandleEvent(aPosition
);
409 if (mIsWatchPositionRequest
&& !mShutdown
) {
412 MOZ_ASSERT(mShutdown
|| mIsWatchPositionRequest
,
413 "non-shutdown getCurrentPosition request after callback!");
416 nsIPrincipal
* nsGeolocationRequest::GetPrincipal() {
420 return mLocator
->GetPrincipal();
424 nsGeolocationRequest::Update(nsIDOMGeoPosition
* aPosition
) {
425 if (!mSeenAnySignal
) {
426 mSeenAnySignal
= true;
427 glean::geolocation::request_result
428 .EnumGet(glean::geolocation::RequestResultLabel::eSuccess
)
431 nsCOMPtr
<nsIRunnable
> ev
= new RequestSendLocationEvent(aPosition
, this);
432 mMainThreadSerialEventTarget
->Dispatch(ev
.forget());
436 static glean::geolocation::RequestResultLabel
MapErrorToLabel(
437 uint16_t aErrorCode
) {
438 using Label
= glean::geolocation::RequestResultLabel
;
439 switch (aErrorCode
) {
440 case GeolocationPositionError_Binding::PERMISSION_DENIED
:
441 return Label::ePermissionDenied
;
442 case GeolocationPositionError_Binding::POSITION_UNAVAILABLE
:
443 return Label::ePositionUnavailable
;
444 case GeolocationPositionError_Binding::TIMEOUT
:
445 return Label::eTimeout
;
447 MOZ_CRASH("Unknown geolocation error label");
448 return Label::ePositionUnavailable
;
453 nsGeolocationRequest::NotifyError(uint16_t aErrorCode
) {
455 gGeolocationLog
, LogLevel::Debug
,
456 ("nsGeolocationRequest::NotifyError with error code: %u", aErrorCode
));
457 MOZ_ASSERT(NS_IsMainThread());
458 if (!mSeenAnySignal
) {
459 mSeenAnySignal
= true;
460 glean::geolocation::request_result
.EnumGet(MapErrorToLabel(aErrorCode
))
463 RefPtr
<GeolocationPositionError
> positionError
=
464 new GeolocationPositionError(mLocator
, static_cast<int16_t>(aErrorCode
));
465 positionError
->NotifyCallback(mErrorCallback
);
469 void nsGeolocationRequest::Shutdown() {
470 MOZ_ASSERT(!mShutdown
, "request shutdown twice");
475 // If there are no other high accuracy requests, the geolocation service will
476 // notify the provider to switch to the default accuracy.
477 if (mOptions
&& mOptions
->mEnableHighAccuracy
) {
478 RefPtr
<nsGeolocationService
> gs
=
479 nsGeolocationService::GetGeolocationService();
481 gs
->UpdateAccuracy();
486 ////////////////////////////////////////////////////
487 // nsGeolocationRequest::TimerCallbackHolder
488 ////////////////////////////////////////////////////
490 NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder
, nsITimerCallback
,
494 nsGeolocationRequest::TimerCallbackHolder::Notify(nsITimer
*) {
495 if (mRequest
&& mRequest
->mLocator
) {
496 RefPtr
<nsGeolocationRequest
> request(mRequest
);
503 ////////////////////////////////////////////////////
504 // nsGeolocationService
505 ////////////////////////////////////////////////////
506 NS_INTERFACE_MAP_BEGIN(nsGeolocationService
)
507 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIGeolocationUpdate
)
508 NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate
)
509 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
512 NS_IMPL_ADDREF(nsGeolocationService
)
513 NS_IMPL_RELEASE(nsGeolocationService
)
515 nsresult
nsGeolocationService::Init() {
516 if (!StaticPrefs::geo_enabled()) {
517 return NS_ERROR_FAILURE
;
520 if (XRE_IsContentProcess()) {
524 // geolocation service can be enabled -> now register observer
525 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
527 return NS_ERROR_FAILURE
;
530 obs
->AddObserver(this, "xpcom-shutdown", false);
532 #ifdef MOZ_WIDGET_ANDROID
533 mProvider
= new AndroidLocationProvider();
536 #ifdef MOZ_WIDGET_GTK
537 # ifdef MOZ_ENABLE_DBUS
538 if (!mProvider
&& widget::ShouldUsePortal(widget::PortalKind::Location
)) {
539 mProvider
= new PortalLocationProvider();
540 MOZ_LOG(gGeolocationLog
, LogLevel::Debug
,
541 ("Selected PortalLocationProvider"));
542 glean::geolocation::linux_provider
543 .EnumGet(glean::geolocation::LinuxProviderLabel::ePortal
)
546 // Geoclue includes GPS data so it has higher priority than raw GPSD
547 if (!mProvider
&& StaticPrefs::geo_provider_use_geoclue()) {
548 nsCOMPtr
<nsIGeolocationProvider
> gcProvider
= new GeoclueLocationProvider();
549 MOZ_LOG(gGeolocationLog
, LogLevel::Debug
,
550 ("Checking GeoclueLocationProvider"));
551 // The Startup() method will only succeed if Geoclue is available on D-Bus
552 if (NS_SUCCEEDED(gcProvider
->Startup())) {
553 gcProvider
->Shutdown();
554 mProvider
= std::move(gcProvider
);
555 MOZ_LOG(gGeolocationLog
, LogLevel::Debug
,
556 ("Selected GeoclueLocationProvider"));
557 glean::geolocation::linux_provider
558 .EnumGet(glean::geolocation::LinuxProviderLabel::eGeoclue
)
563 if (!mProvider
&& Preferences::GetBool("geo.provider.use_gpsd", false)) {
564 mProvider
= new GpsdLocationProvider();
565 MOZ_LOG(gGeolocationLog
, LogLevel::Debug
,
566 ("Selected GpsdLocationProvider"));
567 glean::geolocation::linux_provider
568 .EnumGet(glean::geolocation::LinuxProviderLabel::eGpsd
)
575 #ifdef MOZ_WIDGET_COCOA
576 if (Preferences::GetBool("geo.provider.use_corelocation", true)) {
577 mProvider
= new CoreLocationLocationProvider();
582 if (Preferences::GetBool("geo.provider.ms-windows-location", false)) {
583 mProvider
= new WindowsLocationProvider();
587 if (Preferences::GetBool("geo.provider.use_mls", false)) {
588 mProvider
= do_CreateInstance("@mozilla.org/geolocation/mls-provider;1");
591 // Override platform-specific providers with the default (network)
592 // provider while testing. Our tests are currently not meant to exercise
593 // the provider, and some tests rely on the network provider being used.
594 // "geo.provider.testing" is always set for all plain and browser chrome
595 // mochitests, and also for xpcshell tests.
596 if (!mProvider
|| Preferences::GetBool("geo.provider.testing", false)) {
597 nsCOMPtr
<nsIGeolocationProvider
> geoTestProvider
=
598 do_GetService(NS_GEOLOCATION_PROVIDER_CONTRACTID
);
600 if (geoTestProvider
) {
601 mProvider
= geoTestProvider
;
608 nsGeolocationService::~nsGeolocationService() = default;
611 nsGeolocationService::Observe(nsISupports
* aSubject
, const char* aTopic
,
612 const char16_t
* aData
) {
613 if (!strcmp("xpcom-shutdown", aTopic
)) {
614 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
616 obs
->RemoveObserver(this, "xpcom-shutdown");
619 for (uint32_t i
= 0; i
< mGeolocators
.Length(); i
++) {
620 mGeolocators
[i
]->Shutdown();
627 if (!strcmp("timer-callback", aTopic
)) {
628 // decide if we can close down the service.
629 for (uint32_t i
= 0; i
< mGeolocators
.Length(); i
++)
630 if (mGeolocators
[i
]->HasActiveCallbacks()) {
631 SetDisconnectTimer();
641 return NS_ERROR_FAILURE
;
645 nsGeolocationService::Update(nsIDOMGeoPosition
* aSomewhere
) {
647 SetCachedPosition(aSomewhere
);
650 for (uint32_t i
= 0; i
< mGeolocators
.Length(); i
++) {
651 mGeolocators
[i
]->Update(aSomewhere
);
658 nsGeolocationService::NotifyError(uint16_t aErrorCode
) {
660 gGeolocationLog
, LogLevel::Debug
,
661 ("nsGeolocationService::NotifyError with error code: %u", aErrorCode
));
662 // nsTArray doesn't have a constructors that takes a different-type TArray.
663 nsTArray
<RefPtr
<Geolocation
>> geolocators
;
664 geolocators
.AppendElements(mGeolocators
);
665 for (uint32_t i
= 0; i
< geolocators
.Length(); i
++) {
666 // MOZ_KnownLive because the stack array above keeps it alive.
667 MOZ_KnownLive(geolocators
[i
])->NotifyError(aErrorCode
);
672 void nsGeolocationService::SetCachedPosition(nsIDOMGeoPosition
* aPosition
) {
673 mLastPosition
.position
= aPosition
;
674 mLastPosition
.isHighAccuracy
= mHigherAccuracy
;
677 CachedPositionAndAccuracy
nsGeolocationService::GetCachedPosition() {
678 return mLastPosition
;
681 nsresult
nsGeolocationService::StartDevice() {
682 if (!StaticPrefs::geo_enabled()) {
683 return NS_ERROR_NOT_AVAILABLE
;
686 // We do not want to keep the geolocation devices online
688 // Close them down after a reasonable period of inactivivity.
689 SetDisconnectTimer();
691 if (XRE_IsContentProcess()) {
692 ContentChild
* cpc
= ContentChild::GetSingleton();
693 cpc
->SendAddGeolocationListener(HighAccuracyRequested());
698 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
700 return NS_ERROR_FAILURE
;
704 return NS_ERROR_FAILURE
;
709 if (NS_FAILED(rv
= mProvider
->Startup()) ||
710 NS_FAILED(rv
= mProvider
->Watch(this))) {
711 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE
);
715 obs
->NotifyObservers(mProvider
, "geolocation-device-events", u
"starting");
720 void nsGeolocationService::SetDisconnectTimer() {
721 if (!mDisconnectTimer
) {
722 mDisconnectTimer
= NS_NewTimer();
724 mDisconnectTimer
->Cancel();
727 mDisconnectTimer
->Init(this, StaticPrefs::geo_timeout(),
728 nsITimer::TYPE_ONE_SHOT
);
731 bool nsGeolocationService::HighAccuracyRequested() {
732 for (uint32_t i
= 0; i
< mGeolocators
.Length(); i
++) {
733 if (mGeolocators
[i
]->HighAccuracyRequested()) {
741 void nsGeolocationService::UpdateAccuracy(bool aForceHigh
) {
742 bool highRequired
= aForceHigh
|| HighAccuracyRequested();
744 if (XRE_IsContentProcess()) {
745 ContentChild
* cpc
= ContentChild::GetSingleton();
746 if (cpc
->IsAlive()) {
747 cpc
->SendSetGeolocationHigherAccuracy(highRequired
);
753 mProvider
->SetHighAccuracy(!mHigherAccuracy
&& highRequired
);
754 mHigherAccuracy
= highRequired
;
757 void nsGeolocationService::StopDevice() {
758 if (mDisconnectTimer
) {
759 mDisconnectTimer
->Cancel();
760 mDisconnectTimer
= nullptr;
763 if (XRE_IsContentProcess()) {
764 ContentChild
* cpc
= ContentChild::GetSingleton();
765 cpc
->SendRemoveGeolocationListener();
767 return; // bail early
770 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
779 mHigherAccuracy
= false;
781 mProvider
->Shutdown();
782 obs
->NotifyObservers(mProvider
, "geolocation-device-events", u
"shutdown");
785 StaticRefPtr
<nsGeolocationService
> nsGeolocationService::sService
;
787 already_AddRefed
<nsGeolocationService
>
788 nsGeolocationService::GetGeolocationService() {
789 RefPtr
<nsGeolocationService
> result
;
790 if (nsGeolocationService::sService
) {
791 result
= nsGeolocationService::sService
;
793 return result
.forget();
796 result
= new nsGeolocationService();
797 if (NS_FAILED(result
->Init())) {
801 ClearOnShutdown(&nsGeolocationService::sService
);
802 nsGeolocationService::sService
= result
;
803 return result
.forget();
806 void nsGeolocationService::AddLocator(Geolocation
* aLocator
) {
807 mGeolocators
.AppendElement(aLocator
);
810 void nsGeolocationService::RemoveLocator(Geolocation
* aLocator
) {
811 mGeolocators
.RemoveElement(aLocator
);
814 ////////////////////////////////////////////////////
816 ////////////////////////////////////////////////////
818 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Geolocation
)
819 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
820 NS_INTERFACE_MAP_ENTRY(nsISupports
)
821 NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate
)
824 NS_IMPL_CYCLE_COLLECTING_ADDREF(Geolocation
)
825 NS_IMPL_CYCLE_COLLECTING_RELEASE(Geolocation
)
827 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Geolocation
, mPendingCallbacks
,
828 mWatchingCallbacks
, mPendingRequests
)
830 Geolocation::Geolocation()
831 : mProtocolType(ProtocolType::OTHER
), mLastWatchId(1) {}
833 Geolocation::~Geolocation() {
839 StaticRefPtr
<Geolocation
> Geolocation::sNonWindowSingleton
;
841 already_AddRefed
<Geolocation
> Geolocation::NonWindowSingleton() {
842 if (sNonWindowSingleton
) {
843 return do_AddRef(sNonWindowSingleton
);
846 RefPtr
<Geolocation
> result
= new Geolocation();
847 DebugOnly
<nsresult
> rv
= result
->Init();
848 MOZ_ASSERT(NS_SUCCEEDED(rv
), "How can this fail?");
850 ClearOnShutdown(&sNonWindowSingleton
);
851 sNonWindowSingleton
= result
;
852 return result
.forget();
855 nsresult
Geolocation::Init(nsPIDOMWindowInner
* aContentDom
) {
856 // Remember the window
858 mOwner
= do_GetWeakReference(aContentDom
);
860 return NS_ERROR_FAILURE
;
863 // Grab the principal of the document
864 nsCOMPtr
<Document
> doc
= aContentDom
->GetDoc();
866 return NS_ERROR_FAILURE
;
869 mPrincipal
= doc
->NodePrincipal();
870 // Store the protocol to send via telemetry later.
871 if (mPrincipal
->SchemeIs("http")) {
872 mProtocolType
= ProtocolType::HTTP
;
873 } else if (mPrincipal
->SchemeIs("https")) {
874 mProtocolType
= ProtocolType::HTTPS
;
878 // If no aContentDom was passed into us, we are being used
879 // by chrome/c++ and have no mOwner, no mPrincipal, and no need
881 mService
= nsGeolocationService::GetGeolocationService();
883 mService
->AddLocator(this);
889 void Geolocation::Shutdown() {
890 // Release all callbacks
891 mPendingCallbacks
.Clear();
892 mWatchingCallbacks
.Clear();
895 mService
->RemoveLocator(this);
896 mService
->UpdateAccuracy();
900 mPrincipal
= nullptr;
903 nsPIDOMWindowInner
* Geolocation::GetParentObject() const {
904 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryReferent(mOwner
);
908 bool Geolocation::HasActiveCallbacks() {
909 return mPendingCallbacks
.Length() || mWatchingCallbacks
.Length();
912 bool Geolocation::HighAccuracyRequested() {
913 for (uint32_t i
= 0; i
< mWatchingCallbacks
.Length(); i
++) {
914 if (mWatchingCallbacks
[i
]->WantsHighAccuracy()) {
919 for (uint32_t i
= 0; i
< mPendingCallbacks
.Length(); i
++) {
920 if (mPendingCallbacks
[i
]->WantsHighAccuracy()) {
928 void Geolocation::RemoveRequest(nsGeolocationRequest
* aRequest
) {
929 bool requestWasKnown
= (mPendingCallbacks
.RemoveElement(aRequest
) !=
930 mWatchingCallbacks
.RemoveElement(aRequest
));
932 Unused
<< requestWasKnown
;
936 Geolocation::Update(nsIDOMGeoPosition
* aSomewhere
) {
937 if (!WindowOwnerStillExists()) {
942 // Don't update position if window is not fully active or the document is
943 // hidden. We keep the pending callaback and watchers waiting for the next
945 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryReferent(this->GetOwner());
947 nsCOMPtr
<Document
> document
= window
->GetDoc();
948 bool isHidden
= document
&& document
->Hidden();
949 if (isHidden
|| !window
->IsFullyActive()) {
955 nsCOMPtr
<nsIDOMGeoPositionCoords
> coords
;
956 aSomewhere
->GetCoords(getter_AddRefs(coords
));
958 double accuracy
= -1;
959 coords
->GetAccuracy(&accuracy
);
960 mozilla::Telemetry::Accumulate(
961 mozilla::Telemetry::GEOLOCATION_ACCURACY_EXPONENTIAL
,
962 static_cast<uint32_t>(accuracy
));
963 glean::geolocation::accuracy
.AccumulateSingleSample(
964 static_cast<uint64_t>(accuracy
));
968 for (uint32_t i
= mPendingCallbacks
.Length(); i
> 0; i
--) {
969 mPendingCallbacks
[i
- 1]->Update(aSomewhere
);
970 RemoveRequest(mPendingCallbacks
[i
- 1]);
973 // notify everyone that is watching
974 for (uint32_t i
= 0; i
< mWatchingCallbacks
.Length(); i
++) {
975 mWatchingCallbacks
[i
]->Update(aSomewhere
);
982 Geolocation::NotifyError(uint16_t aErrorCode
) {
983 MOZ_LOG(gGeolocationLog
, LogLevel::Debug
,
984 ("Geolocation::NotifyError with error code: %u", aErrorCode
));
985 if (!WindowOwnerStillExists()) {
990 mozilla::Telemetry::Accumulate(mozilla::Telemetry::GEOLOCATION_ERROR
, true);
992 for (uint32_t i
= mPendingCallbacks
.Length(); i
> 0; i
--) {
993 RefPtr
<nsGeolocationRequest
> request
= mPendingCallbacks
[i
- 1];
994 request
->NotifyErrorAndShutdown(aErrorCode
);
995 // NotifyErrorAndShutdown() removes the request from the array
998 // notify everyone that is watching
999 for (uint32_t i
= 0; i
< mWatchingCallbacks
.Length(); i
++) {
1000 RefPtr
<nsGeolocationRequest
> request
= mWatchingCallbacks
[i
];
1001 request
->NotifyErrorAndShutdown(aErrorCode
);
1007 bool Geolocation::IsFullyActiveOrChrome() {
1008 // For regular content window, only allow this proceed if the window is "fully
1010 if (nsPIDOMWindowInner
* window
= this->GetParentObject()) {
1011 return window
->IsFullyActive();
1013 // Calls coming from chrome code don't have window, so we can proceed.
1017 bool Geolocation::IsAlreadyCleared(nsGeolocationRequest
* aRequest
) {
1018 for (uint32_t i
= 0, length
= mClearedWatchIDs
.Length(); i
< length
; ++i
) {
1019 if (mClearedWatchIDs
[i
] == aRequest
->WatchId()) {
1027 bool Geolocation::ShouldBlockInsecureRequests() const {
1028 if (Preferences::GetBool(PREF_GEO_SECURITY_ALLOWINSECURE
, false)) {
1032 nsCOMPtr
<nsPIDOMWindowInner
> win
= do_QueryReferent(mOwner
);
1037 nsCOMPtr
<Document
> doc
= win
->GetDoc();
1042 if (!nsGlobalWindowInner::Cast(win
)->IsSecureContext()) {
1043 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag
, "DOM"_ns
, doc
,
1044 nsContentUtils::eDOM_PROPERTIES
,
1045 "GeolocationInsecureRequestIsForbidden");
1052 bool Geolocation::ClearPendingRequest(nsGeolocationRequest
* aRequest
) {
1053 if (aRequest
->IsWatch() && this->IsAlreadyCleared(aRequest
)) {
1054 this->NotifyAllowedRequest(aRequest
);
1055 this->ClearWatch(aRequest
->WatchId());
1062 void Geolocation::GetCurrentPosition(PositionCallback
& aCallback
,
1063 PositionErrorCallback
* aErrorCallback
,
1064 const PositionOptions
& aOptions
,
1065 CallerType aCallerType
, ErrorResult
& aRv
) {
1066 nsresult rv
= GetCurrentPosition(
1067 GeoPositionCallback(&aCallback
), GeoPositionErrorCallback(aErrorCallback
),
1068 CreatePositionOptionsCopy(aOptions
), aCallerType
);
1070 if (NS_FAILED(rv
)) {
1075 nsresult
Geolocation::GetCurrentPosition(GeoPositionCallback callback
,
1076 GeoPositionErrorCallback errorCallback
,
1077 UniquePtr
<PositionOptions
>&& options
,
1078 CallerType aCallerType
) {
1079 if (!IsFullyActiveOrChrome()) {
1080 RefPtr
<GeolocationPositionError
> positionError
=
1081 new GeolocationPositionError(
1082 this, GeolocationPositionError_Binding::POSITION_UNAVAILABLE
);
1083 positionError
->NotifyCallback(errorCallback
);
1087 if (mPendingCallbacks
.Length() > MAX_GEO_REQUESTS_PER_WINDOW
) {
1088 return NS_ERROR_NOT_AVAILABLE
;
1091 // After this we hand over ownership of options to our nsGeolocationRequest.
1093 nsIEventTarget
* target
= GetMainThreadSerialEventTarget();
1094 RefPtr
<nsGeolocationRequest
> request
= new nsGeolocationRequest(
1095 this, std::move(callback
), std::move(errorCallback
), std::move(options
),
1098 if (!StaticPrefs::geo_enabled() || ShouldBlockInsecureRequests() ||
1099 !request
->CheckPermissionDelegate()) {
1100 request
->RequestDelayedTask(target
,
1101 nsGeolocationRequest::DelayedTaskType::Deny
);
1105 if (!mOwner
&& aCallerType
!= CallerType::System
) {
1106 return NS_ERROR_FAILURE
;
1110 if (!RegisterRequestWithPrompt(request
)) {
1111 return NS_ERROR_NOT_AVAILABLE
;
1117 if (aCallerType
!= CallerType::System
) {
1118 return NS_ERROR_FAILURE
;
1121 request
->RequestDelayedTask(target
,
1122 nsGeolocationRequest::DelayedTaskType::Allow
);
1127 int32_t Geolocation::WatchPosition(PositionCallback
& aCallback
,
1128 PositionErrorCallback
* aErrorCallback
,
1129 const PositionOptions
& aOptions
,
1130 CallerType aCallerType
, ErrorResult
& aRv
) {
1131 return WatchPosition(GeoPositionCallback(&aCallback
),
1132 GeoPositionErrorCallback(aErrorCallback
),
1133 CreatePositionOptionsCopy(aOptions
), aCallerType
, aRv
);
1136 int32_t Geolocation::WatchPosition(
1137 nsIDOMGeoPositionCallback
* aCallback
,
1138 nsIDOMGeoPositionErrorCallback
* aErrorCallback
,
1139 UniquePtr
<PositionOptions
>&& aOptions
) {
1140 MOZ_ASSERT(aCallback
);
1142 return WatchPosition(GeoPositionCallback(aCallback
),
1143 GeoPositionErrorCallback(aErrorCallback
),
1144 std::move(aOptions
), CallerType::System
, IgnoreErrors());
1147 // On errors we return 0 because that's not a valid watch id and will
1148 // get ignored in clearWatch.
1149 int32_t Geolocation::WatchPosition(GeoPositionCallback aCallback
,
1150 GeoPositionErrorCallback aErrorCallback
,
1151 UniquePtr
<PositionOptions
>&& aOptions
,
1152 CallerType aCallerType
, ErrorResult
& aRv
) {
1153 if (!IsFullyActiveOrChrome()) {
1154 RefPtr
<GeolocationPositionError
> positionError
=
1155 new GeolocationPositionError(
1156 this, GeolocationPositionError_Binding::POSITION_UNAVAILABLE
);
1157 positionError
->NotifyCallback(aErrorCallback
);
1161 if (mWatchingCallbacks
.Length() > MAX_GEO_REQUESTS_PER_WINDOW
) {
1162 aRv
.Throw(NS_ERROR_NOT_AVAILABLE
);
1167 int32_t watchId
= mLastWatchId
++;
1169 nsIEventTarget
* target
= GetMainThreadSerialEventTarget();
1170 RefPtr
<nsGeolocationRequest
> request
= new nsGeolocationRequest(
1171 this, std::move(aCallback
), std::move(aErrorCallback
),
1172 std::move(aOptions
), target
, true, watchId
);
1174 if (!StaticPrefs::geo_enabled() || ShouldBlockInsecureRequests() ||
1175 !request
->CheckPermissionDelegate()) {
1176 request
->RequestDelayedTask(target
,
1177 nsGeolocationRequest::DelayedTaskType::Deny
);
1181 if (!mOwner
&& aCallerType
!= CallerType::System
) {
1182 aRv
.Throw(NS_ERROR_FAILURE
);
1187 if (!RegisterRequestWithPrompt(request
)) {
1188 aRv
.Throw(NS_ERROR_NOT_AVAILABLE
);
1195 if (aCallerType
!= CallerType::System
) {
1196 aRv
.Throw(NS_ERROR_FAILURE
);
1200 request
->Allow(JS::UndefinedHandleValue
);
1204 void Geolocation::ClearWatch(int32_t aWatchId
) {
1209 if (!mClearedWatchIDs
.Contains(aWatchId
)) {
1210 mClearedWatchIDs
.AppendElement(aWatchId
);
1213 for (uint32_t i
= 0, length
= mWatchingCallbacks
.Length(); i
< length
; ++i
) {
1214 if (mWatchingCallbacks
[i
]->WatchId() == aWatchId
) {
1215 mWatchingCallbacks
[i
]->Shutdown();
1216 RemoveRequest(mWatchingCallbacks
[i
]);
1217 mClearedWatchIDs
.RemoveElement(aWatchId
);
1222 // make sure we also search through the pending requests lists for
1223 // watches to clear...
1224 for (uint32_t i
= 0, length
= mPendingRequests
.Length(); i
< length
; ++i
) {
1225 if (mPendingRequests
[i
]->IsWatch() &&
1226 (mPendingRequests
[i
]->WatchId() == aWatchId
)) {
1227 mPendingRequests
[i
]->Shutdown();
1228 mPendingRequests
.RemoveElementAt(i
);
1234 bool Geolocation::WindowOwnerStillExists() {
1235 // an owner was never set when Geolocation
1236 // was created, which means that this object
1237 // is being used without a window.
1238 if (mOwner
== nullptr) {
1242 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryReferent(mOwner
);
1245 nsPIDOMWindowOuter
* outer
= window
->GetOuterWindow();
1246 if (!outer
|| outer
->GetCurrentInnerWindow() != window
|| outer
->Closed()) {
1254 void Geolocation::NotifyAllowedRequest(nsGeolocationRequest
* aRequest
) {
1255 if (aRequest
->IsWatch()) {
1256 mWatchingCallbacks
.AppendElement(aRequest
);
1258 mPendingCallbacks
.AppendElement(aRequest
);
1262 bool Geolocation::RegisterRequestWithPrompt(nsGeolocationRequest
* request
) {
1263 nsIEventTarget
* target
= GetMainThreadSerialEventTarget();
1264 ContentPermissionRequestBase::PromptResult pr
= request
->CheckPromptPrefs();
1265 if (pr
== ContentPermissionRequestBase::PromptResult::Granted
) {
1266 request
->RequestDelayedTask(target
,
1267 nsGeolocationRequest::DelayedTaskType::Allow
);
1270 if (pr
== ContentPermissionRequestBase::PromptResult::Denied
) {
1271 request
->RequestDelayedTask(target
,
1272 nsGeolocationRequest::DelayedTaskType::Deny
);
1276 request
->RequestDelayedTask(target
,
1277 nsGeolocationRequest::DelayedTaskType::Request
);
1281 JSObject
* Geolocation::WrapObject(JSContext
* aCtx
,
1282 JS::Handle
<JSObject
*> aGivenProto
) {
1283 return Geolocation_Binding::Wrap(aCtx
, this, aGivenProto
);