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/Preferences.h"
16 #include "mozilla/Services.h"
17 #include "mozilla/StaticPrefs_geo.h"
18 #include "mozilla/Telemetry.h"
19 #include "mozilla/UniquePtr.h"
20 #include "mozilla/Unused.h"
21 #include "mozilla/WeakPtr.h"
22 #include "mozilla/EventStateManager.h"
23 #include "nsComponentManagerUtils.h"
24 #include "nsContentPermissionHelper.h"
25 #include "nsContentUtils.h"
26 #include "nsGlobalWindow.h"
27 #include "mozilla/dom/Document.h"
29 #include "nsIObserverService.h"
30 #include "nsIScriptError.h"
31 #include "nsPIDOMWindow.h"
32 #include "nsServiceManagerUtils.h"
33 #include "nsThreadUtils.h"
34 #include "nsXULAppAPI.h"
38 #ifdef MOZ_WIDGET_ANDROID
39 # include "AndroidLocationProvider.h"
43 # include "GpsdLocationProvider.h"
46 #ifdef MOZ_ENABLE_DBUS
47 # include "mozilla/WidgetUtilsGtk.h"
48 # include "GeoclueLocationProvider.h"
49 # include "PortalLocationProvider.h"
52 #ifdef MOZ_WIDGET_COCOA
53 # include "CoreLocationLocationProvider.h"
57 # include "WindowsLocationProvider.h"
58 # include "mozilla/WindowsVersion.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 class nsGeolocationRequest final
: public ContentPermissionRequestBase
,
74 public nsIGeolocationUpdate
,
75 public SupportsWeakPtr
{
77 NS_DECL_ISUPPORTS_INHERITED
78 NS_DECL_NSIGEOLOCATIONUPDATE
80 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsGeolocationRequest
,
81 ContentPermissionRequestBase
)
83 nsGeolocationRequest(Geolocation
* aLocator
, GeoPositionCallback aCallback
,
84 GeoPositionErrorCallback aErrorCallback
,
85 UniquePtr
<PositionOptions
>&& aOptions
,
86 nsIEventTarget
* aMainThreadTarget
,
87 bool aWatchPositionRequest
= false,
88 int32_t aWatchId
= 0);
90 // nsIContentPermissionRequest
91 MOZ_CAN_RUN_SCRIPT NS_IMETHOD
Cancel(void) override
;
92 MOZ_CAN_RUN_SCRIPT NS_IMETHOD
Allow(JS::Handle
<JS::Value
> choices
) override
;
96 // MOZ_CAN_RUN_SCRIPT_BOUNDARY is OK here because we're always called from a
97 // runnable. Ideally nsIRunnable::Run and its overloads would just be
98 // MOZ_CAN_RUN_SCRIPT and then we could be too...
99 MOZ_CAN_RUN_SCRIPT_BOUNDARY
100 void SendLocation(nsIDOMGeoPosition
* aLocation
);
101 bool WantsHighAccuracy() {
102 return !mShutdown
&& mOptions
&& mOptions
->mEnableHighAccuracy
;
104 void SetTimeoutTimer();
105 void StopTimeoutTimer();
107 void NotifyErrorAndShutdown(uint16_t);
108 using ContentPermissionRequestBase::GetPrincipal
;
109 nsIPrincipal
* GetPrincipal();
111 bool IsWatch() { return mIsWatchPositionRequest
; }
112 int32_t WatchId() { return mWatchId
; }
115 virtual ~nsGeolocationRequest();
117 class TimerCallbackHolder final
: public nsITimerCallback
, public nsINamed
{
120 NS_DECL_NSITIMERCALLBACK
122 explicit TimerCallbackHolder(nsGeolocationRequest
* aRequest
)
123 : mRequest(aRequest
) {}
125 NS_IMETHOD
GetName(nsACString
& aName
) override
{
126 aName
.AssignLiteral("nsGeolocationRequest::TimerCallbackHolder");
131 ~TimerCallbackHolder() = default;
132 WeakPtr
<nsGeolocationRequest
> mRequest
;
135 // Only called from a timer, so MOZ_CAN_RUN_SCRIPT_BOUNDARY ok for now.
136 MOZ_CAN_RUN_SCRIPT_BOUNDARY
void Notify();
138 bool mIsWatchPositionRequest
;
140 nsCOMPtr
<nsITimer
> mTimeoutTimer
;
141 GeoPositionCallback mCallback
;
142 GeoPositionErrorCallback mErrorCallback
;
143 UniquePtr
<PositionOptions
> mOptions
;
145 RefPtr
<Geolocation
> mLocator
;
149 nsCOMPtr
<nsIEventTarget
> mMainThreadTarget
;
152 static UniquePtr
<PositionOptions
> CreatePositionOptionsCopy(
153 const PositionOptions
& aOptions
) {
154 UniquePtr
<PositionOptions
> geoOptions
= MakeUnique
<PositionOptions
>();
156 geoOptions
->mEnableHighAccuracy
= aOptions
.mEnableHighAccuracy
;
157 geoOptions
->mMaximumAge
= aOptions
.mMaximumAge
;
158 geoOptions
->mTimeout
= aOptions
.mTimeout
;
163 class RequestSendLocationEvent
: public Runnable
{
165 RequestSendLocationEvent(nsIDOMGeoPosition
* aPosition
,
166 nsGeolocationRequest
* aRequest
)
167 : mozilla::Runnable("RequestSendLocationEvent"),
168 mPosition(aPosition
),
169 mRequest(aRequest
) {}
171 NS_IMETHOD
Run() override
{
172 mRequest
->SendLocation(mPosition
);
177 nsCOMPtr
<nsIDOMGeoPosition
> mPosition
;
178 RefPtr
<nsGeolocationRequest
> mRequest
;
179 RefPtr
<Geolocation
> mLocator
;
182 ////////////////////////////////////////////////////
183 // nsGeolocationRequest
184 ////////////////////////////////////////////////////
186 static nsPIDOMWindowInner
* ConvertWeakReferenceToWindow(
187 nsIWeakReference
* aWeakPtr
) {
188 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryReferent(aWeakPtr
);
189 // This isn't usually safe, but here we're just extracting a raw pointer in
190 // order to pass it to a base class constructor which will in turn convert it
191 // into a strong pointer for us.
192 nsPIDOMWindowInner
* raw
= window
.get();
196 nsGeolocationRequest::nsGeolocationRequest(
197 Geolocation
* aLocator
, GeoPositionCallback aCallback
,
198 GeoPositionErrorCallback aErrorCallback
,
199 UniquePtr
<PositionOptions
>&& aOptions
, nsIEventTarget
* aMainThreadTarget
,
200 bool aWatchPositionRequest
, int32_t aWatchId
)
201 : ContentPermissionRequestBase(
202 aLocator
->GetPrincipal(),
203 ConvertWeakReferenceToWindow(aLocator
->GetOwner()), "geo"_ns
,
205 mIsWatchPositionRequest(aWatchPositionRequest
),
206 mCallback(std::move(aCallback
)),
207 mErrorCallback(std::move(aErrorCallback
)),
208 mOptions(std::move(aOptions
)),
212 mMainThreadTarget(aMainThreadTarget
) {
213 if (nsCOMPtr
<nsPIDOMWindowInner
> win
=
214 do_QueryReferent(mLocator
->GetOwner())) {
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() {
232 NotifyErrorAndShutdown(GeolocationPositionError_Binding::TIMEOUT
);
235 void nsGeolocationRequest::NotifyErrorAndShutdown(uint16_t aErrorCode
) {
236 MOZ_ASSERT(!mShutdown
, "timeout after shutdown");
237 if (!mIsWatchPositionRequest
) {
239 mLocator
->RemoveRequest(this);
242 NotifyError(aErrorCode
);
246 nsGeolocationRequest::Cancel() {
247 if (mLocator
->ClearPendingRequest(this)) {
251 NotifyError(GeolocationPositionError_Binding::PERMISSION_DENIED
);
256 nsGeolocationRequest::Allow(JS::Handle
<JS::Value
> aChoices
) {
257 MOZ_ASSERT(aChoices
.isUndefined());
259 if (mLocator
->ClearPendingRequest(this)) {
263 RefPtr
<nsGeolocationService
> gs
=
264 nsGeolocationService::GetGeolocationService();
266 bool canUseCache
= false;
267 CachedPositionAndAccuracy lastPosition
= gs
->GetCachedPosition();
268 if (lastPosition
.position
) {
269 EpochTimeStamp cachedPositionTime_ms
;
270 lastPosition
.position
->GetTimestamp(&cachedPositionTime_ms
);
271 // check to see if we can use a cached value
272 // if the user has specified a maximumAge, return a cached value.
273 if (mOptions
&& mOptions
->mMaximumAge
> 0) {
274 uint32_t maximumAge_ms
= mOptions
->mMaximumAge
;
275 bool isCachedWithinRequestedAccuracy
=
276 WantsHighAccuracy() <= lastPosition
.isHighAccuracy
;
277 bool isCachedWithinRequestedTime
=
278 EpochTimeStamp(PR_Now() / PR_USEC_PER_MSEC
- maximumAge_ms
) <=
279 cachedPositionTime_ms
;
281 isCachedWithinRequestedAccuracy
&& isCachedWithinRequestedTime
;
285 gs
->UpdateAccuracy(WantsHighAccuracy());
287 // okay, we can return a cached position
288 // getCurrentPosition requests serviced by the cache
289 // will now be owned by the RequestSendLocationEvent
290 Update(lastPosition
.position
);
292 // After Update is called, getCurrentPosition finishes it's job.
293 if (!mIsWatchPositionRequest
) {
298 // if it is not a watch request and timeout is 0,
299 // invoke the errorCallback (if present) with TIMEOUT code
300 if (mOptions
&& mOptions
->mTimeout
== 0 && !mIsWatchPositionRequest
) {
301 NotifyError(GeolocationPositionError_Binding::TIMEOUT
);
306 // Non-cached location request
307 bool allowedRequest
= mIsWatchPositionRequest
|| !canUseCache
;
308 if (allowedRequest
) {
309 // let the locator know we're pending
310 // we will now be owned by the locator
311 mLocator
->NotifyAllowedRequest(this);
314 // Kick off the geo device, if it isn't already running
315 nsresult rv
= gs
->StartDevice();
318 if (allowedRequest
) {
319 mLocator
->RemoveRequest(this);
321 // Location provider error
322 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE
);
331 void nsGeolocationRequest::SetTimeoutTimer() {
332 MOZ_ASSERT(!mShutdown
, "set timeout after shutdown");
336 if (mOptions
&& mOptions
->mTimeout
!= 0 && mOptions
->mTimeout
!= 0x7fffffff) {
337 RefPtr
<TimerCallbackHolder
> holder
= new TimerCallbackHolder(this);
338 NS_NewTimerWithCallback(getter_AddRefs(mTimeoutTimer
), holder
,
339 mOptions
->mTimeout
, nsITimer::TYPE_ONE_SHOT
);
343 void nsGeolocationRequest::StopTimeoutTimer() {
345 mTimeoutTimer
->Cancel();
346 mTimeoutTimer
= nullptr;
350 void nsGeolocationRequest::SendLocation(nsIDOMGeoPosition
* aPosition
) {
352 // Ignore SendLocationEvents issued before we were cleared.
356 if (mOptions
&& mOptions
->mMaximumAge
> 0) {
357 EpochTimeStamp positionTime_ms
;
358 aPosition
->GetTimestamp(&positionTime_ms
);
359 const uint32_t maximumAge_ms
= mOptions
->mMaximumAge
;
360 const bool isTooOld
= EpochTimeStamp(PR_Now() / PR_USEC_PER_MSEC
-
361 maximumAge_ms
) > positionTime_ms
;
367 RefPtr
<mozilla::dom::GeolocationPosition
> wrapped
;
370 nsCOMPtr
<nsIDOMGeoPositionCoords
> coords
;
371 aPosition
->GetCoords(getter_AddRefs(coords
));
373 wrapped
= new mozilla::dom::GeolocationPosition(ToSupports(mLocator
),
379 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE
);
383 if (!mIsWatchPositionRequest
) {
384 // Cancel timer and position updates in case the position
385 // callback spins the event loop
389 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
390 obs
->NotifyObservers(wrapped
, "geolocation-position-events",
391 u
"location-updated");
394 if (mCallback
.HasWebIDLCallback()) {
395 RefPtr
<PositionCallback
> callback
= mCallback
.GetWebIDLCallback();
397 MOZ_ASSERT(callback
);
398 callback
->Call(*wrapped
);
400 nsIDOMGeoPositionCallback
* callback
= mCallback
.GetXPCOMCallback();
401 MOZ_ASSERT(callback
);
402 callback
->HandleEvent(aPosition
);
405 if (mIsWatchPositionRequest
&& !mShutdown
) {
408 MOZ_ASSERT(mShutdown
|| mIsWatchPositionRequest
,
409 "non-shutdown getCurrentPosition request after callback!");
412 nsIPrincipal
* nsGeolocationRequest::GetPrincipal() {
416 return mLocator
->GetPrincipal();
420 nsGeolocationRequest::Update(nsIDOMGeoPosition
* aPosition
) {
421 nsCOMPtr
<nsIRunnable
> ev
= new RequestSendLocationEvent(aPosition
, this);
422 mMainThreadTarget
->Dispatch(ev
.forget());
427 nsGeolocationRequest::NotifyError(uint16_t aErrorCode
) {
428 MOZ_ASSERT(NS_IsMainThread());
429 RefPtr
<GeolocationPositionError
> positionError
=
430 new GeolocationPositionError(mLocator
, aErrorCode
);
431 positionError
->NotifyCallback(mErrorCallback
);
435 void nsGeolocationRequest::Shutdown() {
436 MOZ_ASSERT(!mShutdown
, "request shutdown twice");
441 // If there are no other high accuracy requests, the geolocation service will
442 // notify the provider to switch to the default accuracy.
443 if (mOptions
&& mOptions
->mEnableHighAccuracy
) {
444 RefPtr
<nsGeolocationService
> gs
=
445 nsGeolocationService::GetGeolocationService();
447 gs
->UpdateAccuracy();
452 ////////////////////////////////////////////////////
453 // nsGeolocationRequest::TimerCallbackHolder
454 ////////////////////////////////////////////////////
456 NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder
, nsITimerCallback
,
460 nsGeolocationRequest::TimerCallbackHolder::Notify(nsITimer
*) {
461 if (mRequest
&& mRequest
->mLocator
) {
462 RefPtr
<nsGeolocationRequest
> request(mRequest
);
469 ////////////////////////////////////////////////////
470 // nsGeolocationService
471 ////////////////////////////////////////////////////
472 NS_INTERFACE_MAP_BEGIN(nsGeolocationService
)
473 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIGeolocationUpdate
)
474 NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate
)
475 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
478 NS_IMPL_ADDREF(nsGeolocationService
)
479 NS_IMPL_RELEASE(nsGeolocationService
)
481 nsresult
nsGeolocationService::Init() {
482 if (!StaticPrefs::geo_enabled()) {
483 return NS_ERROR_FAILURE
;
486 if (XRE_IsContentProcess()) {
490 // geolocation service can be enabled -> now register observer
491 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
493 return NS_ERROR_FAILURE
;
496 obs
->AddObserver(this, "xpcom-shutdown", false);
498 #ifdef MOZ_WIDGET_ANDROID
499 mProvider
= new AndroidLocationProvider();
502 #ifdef MOZ_WIDGET_GTK
503 # ifdef MOZ_ENABLE_DBUS
504 if (!mProvider
&& widget::ShouldUsePortal(widget::PortalKind::Location
)) {
505 mProvider
= new PortalLocationProvider();
507 // Geoclue includes GPS data so it has higher priority than raw GPSD
508 if (!mProvider
&& StaticPrefs::geo_provider_use_geoclue()) {
509 nsCOMPtr
<nsIGeolocationProvider
> gcProvider
= new GeoclueLocationProvider();
510 // The Startup() method will only succeed if Geoclue is available on D-Bus
511 if (NS_SUCCEEDED(gcProvider
->Startup())) {
512 gcProvider
->Shutdown();
513 mProvider
= std::move(gcProvider
);
517 if (!mProvider
&& Preferences::GetBool("geo.provider.use_gpsd", false)) {
518 mProvider
= new GpsdLocationProvider();
524 #ifdef MOZ_WIDGET_COCOA
525 if (Preferences::GetBool("geo.provider.use_corelocation", true)) {
526 mProvider
= new CoreLocationLocationProvider();
531 if (Preferences::GetBool("geo.provider.ms-windows-location", false) &&
533 mProvider
= new WindowsLocationProvider();
537 if (Preferences::GetBool("geo.provider.use_mls", false)) {
538 mProvider
= do_CreateInstance("@mozilla.org/geolocation/mls-provider;1");
541 // Override platform-specific providers with the default (network)
542 // provider while testing. Our tests are currently not meant to exercise
543 // the provider, and some tests rely on the network provider being used.
544 // "geo.provider.testing" is always set for all plain and browser chrome
545 // mochitests, and also for xpcshell tests.
546 if (!mProvider
|| Preferences::GetBool("geo.provider.testing", false)) {
547 nsCOMPtr
<nsIGeolocationProvider
> geoTestProvider
=
548 do_GetService(NS_GEOLOCATION_PROVIDER_CONTRACTID
);
550 if (geoTestProvider
) {
551 mProvider
= geoTestProvider
;
558 nsGeolocationService::~nsGeolocationService() = default;
561 nsGeolocationService::Observe(nsISupports
* aSubject
, const char* aTopic
,
562 const char16_t
* aData
) {
563 if (!strcmp("xpcom-shutdown", aTopic
)) {
564 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
566 obs
->RemoveObserver(this, "xpcom-shutdown");
569 for (uint32_t i
= 0; i
< mGeolocators
.Length(); i
++) {
570 mGeolocators
[i
]->Shutdown();
577 if (!strcmp("timer-callback", aTopic
)) {
578 // decide if we can close down the service.
579 for (uint32_t i
= 0; i
< mGeolocators
.Length(); i
++)
580 if (mGeolocators
[i
]->HasActiveCallbacks()) {
581 SetDisconnectTimer();
591 return NS_ERROR_FAILURE
;
595 nsGeolocationService::Update(nsIDOMGeoPosition
* aSomewhere
) {
597 SetCachedPosition(aSomewhere
);
600 for (uint32_t i
= 0; i
< mGeolocators
.Length(); i
++) {
601 mGeolocators
[i
]->Update(aSomewhere
);
608 nsGeolocationService::NotifyError(uint16_t aErrorCode
) {
609 // nsTArray doesn't have a constructors that takes a different-type TArray.
610 nsTArray
<RefPtr
<Geolocation
>> geolocators
;
611 geolocators
.AppendElements(mGeolocators
);
612 for (uint32_t i
= 0; i
< geolocators
.Length(); i
++) {
613 // MOZ_KnownLive because the stack array above keeps it alive.
614 MOZ_KnownLive(geolocators
[i
])->NotifyError(aErrorCode
);
619 void nsGeolocationService::SetCachedPosition(nsIDOMGeoPosition
* aPosition
) {
620 mLastPosition
.position
= aPosition
;
621 mLastPosition
.isHighAccuracy
= mHigherAccuracy
;
624 CachedPositionAndAccuracy
nsGeolocationService::GetCachedPosition() {
625 return mLastPosition
;
628 nsresult
nsGeolocationService::StartDevice() {
629 if (!StaticPrefs::geo_enabled()) {
630 return NS_ERROR_NOT_AVAILABLE
;
633 // We do not want to keep the geolocation devices online
635 // Close them down after a reasonable period of inactivivity.
636 SetDisconnectTimer();
638 if (XRE_IsContentProcess()) {
639 ContentChild
* cpc
= ContentChild::GetSingleton();
640 cpc
->SendAddGeolocationListener(HighAccuracyRequested());
645 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
647 return NS_ERROR_FAILURE
;
651 return NS_ERROR_FAILURE
;
656 if (NS_FAILED(rv
= mProvider
->Startup()) ||
657 NS_FAILED(rv
= mProvider
->Watch(this))) {
658 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE
);
662 obs
->NotifyObservers(mProvider
, "geolocation-device-events", u
"starting");
667 void nsGeolocationService::SetDisconnectTimer() {
668 if (!mDisconnectTimer
) {
669 mDisconnectTimer
= NS_NewTimer();
671 mDisconnectTimer
->Cancel();
674 mDisconnectTimer
->Init(this, StaticPrefs::geo_timeout(),
675 nsITimer::TYPE_ONE_SHOT
);
678 bool nsGeolocationService::HighAccuracyRequested() {
679 for (uint32_t i
= 0; i
< mGeolocators
.Length(); i
++) {
680 if (mGeolocators
[i
]->HighAccuracyRequested()) {
688 void nsGeolocationService::UpdateAccuracy(bool aForceHigh
) {
689 bool highRequired
= aForceHigh
|| HighAccuracyRequested();
691 if (XRE_IsContentProcess()) {
692 ContentChild
* cpc
= ContentChild::GetSingleton();
693 if (cpc
->IsAlive()) {
694 cpc
->SendSetGeolocationHigherAccuracy(highRequired
);
700 mProvider
->SetHighAccuracy(!mHigherAccuracy
&& highRequired
);
701 mHigherAccuracy
= highRequired
;
704 void nsGeolocationService::StopDevice() {
705 if (mDisconnectTimer
) {
706 mDisconnectTimer
->Cancel();
707 mDisconnectTimer
= nullptr;
710 if (XRE_IsContentProcess()) {
711 ContentChild
* cpc
= ContentChild::GetSingleton();
712 cpc
->SendRemoveGeolocationListener();
714 return; // bail early
717 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
726 mHigherAccuracy
= false;
728 mProvider
->Shutdown();
729 obs
->NotifyObservers(mProvider
, "geolocation-device-events", u
"shutdown");
732 StaticRefPtr
<nsGeolocationService
> nsGeolocationService::sService
;
734 already_AddRefed
<nsGeolocationService
>
735 nsGeolocationService::GetGeolocationService() {
736 RefPtr
<nsGeolocationService
> result
;
737 if (nsGeolocationService::sService
) {
738 result
= nsGeolocationService::sService
;
740 return result
.forget();
743 result
= new nsGeolocationService();
744 if (NS_FAILED(result
->Init())) {
748 ClearOnShutdown(&nsGeolocationService::sService
);
749 nsGeolocationService::sService
= result
;
750 return result
.forget();
753 void nsGeolocationService::AddLocator(Geolocation
* aLocator
) {
754 mGeolocators
.AppendElement(aLocator
);
757 void nsGeolocationService::RemoveLocator(Geolocation
* aLocator
) {
758 mGeolocators
.RemoveElement(aLocator
);
761 ////////////////////////////////////////////////////
763 ////////////////////////////////////////////////////
765 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Geolocation
)
766 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
767 NS_INTERFACE_MAP_ENTRY(nsISupports
)
768 NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate
)
771 NS_IMPL_CYCLE_COLLECTING_ADDREF(Geolocation
)
772 NS_IMPL_CYCLE_COLLECTING_RELEASE(Geolocation
)
774 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Geolocation
, mPendingCallbacks
,
775 mWatchingCallbacks
, mPendingRequests
)
777 Geolocation::Geolocation()
778 : mProtocolType(ProtocolType::OTHER
), mLastWatchId(1) {}
780 Geolocation::~Geolocation() {
786 StaticRefPtr
<Geolocation
> Geolocation::sNonWindowSingleton
;
788 already_AddRefed
<Geolocation
> Geolocation::NonWindowSingleton() {
789 if (sNonWindowSingleton
) {
790 return do_AddRef(sNonWindowSingleton
);
793 RefPtr
<Geolocation
> result
= new Geolocation();
794 DebugOnly
<nsresult
> rv
= result
->Init();
795 MOZ_ASSERT(NS_SUCCEEDED(rv
), "How can this fail?");
797 ClearOnShutdown(&sNonWindowSingleton
);
798 sNonWindowSingleton
= result
;
799 return result
.forget();
802 nsresult
Geolocation::Init(nsPIDOMWindowInner
* aContentDom
) {
803 // Remember the window
805 mOwner
= do_GetWeakReference(aContentDom
);
807 return NS_ERROR_FAILURE
;
810 // Grab the principal of the document
811 nsCOMPtr
<Document
> doc
= aContentDom
->GetDoc();
813 return NS_ERROR_FAILURE
;
816 mPrincipal
= doc
->NodePrincipal();
817 // Store the protocol to send via telemetry later.
818 if (mPrincipal
->SchemeIs("http")) {
819 mProtocolType
= ProtocolType::HTTP
;
820 } else if (mPrincipal
->SchemeIs("https")) {
821 mProtocolType
= ProtocolType::HTTPS
;
825 // If no aContentDom was passed into us, we are being used
826 // by chrome/c++ and have no mOwner, no mPrincipal, and no need
828 mService
= nsGeolocationService::GetGeolocationService();
830 mService
->AddLocator(this);
836 void Geolocation::Shutdown() {
837 // Release all callbacks
838 mPendingCallbacks
.Clear();
839 mWatchingCallbacks
.Clear();
842 mService
->RemoveLocator(this);
843 mService
->UpdateAccuracy();
847 mPrincipal
= nullptr;
850 nsPIDOMWindowInner
* Geolocation::GetParentObject() const {
851 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryReferent(mOwner
);
855 bool Geolocation::HasActiveCallbacks() {
856 return mPendingCallbacks
.Length() || mWatchingCallbacks
.Length();
859 bool Geolocation::HighAccuracyRequested() {
860 for (uint32_t i
= 0; i
< mWatchingCallbacks
.Length(); i
++) {
861 if (mWatchingCallbacks
[i
]->WantsHighAccuracy()) {
866 for (uint32_t i
= 0; i
< mPendingCallbacks
.Length(); i
++) {
867 if (mPendingCallbacks
[i
]->WantsHighAccuracy()) {
875 void Geolocation::RemoveRequest(nsGeolocationRequest
* aRequest
) {
876 bool requestWasKnown
= (mPendingCallbacks
.RemoveElement(aRequest
) !=
877 mWatchingCallbacks
.RemoveElement(aRequest
));
879 Unused
<< requestWasKnown
;
883 Geolocation::Update(nsIDOMGeoPosition
* aSomewhere
) {
884 if (!WindowOwnerStillExists()) {
889 // Don't update position if window is not fully active or the document is
890 // hidden. We keep the pending callaback and watchers waiting for the next
892 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryReferent(this->GetOwner());
894 nsCOMPtr
<Document
> document
= window
->GetDoc();
895 bool isHidden
= document
&& document
->Hidden();
896 if (isHidden
|| !window
->IsFullyActive()) {
902 nsCOMPtr
<nsIDOMGeoPositionCoords
> coords
;
903 aSomewhere
->GetCoords(getter_AddRefs(coords
));
905 double accuracy
= -1;
906 coords
->GetAccuracy(&accuracy
);
907 mozilla::Telemetry::Accumulate(
908 mozilla::Telemetry::GEOLOCATION_ACCURACY_EXPONENTIAL
, accuracy
);
912 for (uint32_t i
= mPendingCallbacks
.Length(); i
> 0; i
--) {
913 mPendingCallbacks
[i
- 1]->Update(aSomewhere
);
914 RemoveRequest(mPendingCallbacks
[i
- 1]);
917 // notify everyone that is watching
918 for (uint32_t i
= 0; i
< mWatchingCallbacks
.Length(); i
++) {
919 mWatchingCallbacks
[i
]->Update(aSomewhere
);
926 Geolocation::NotifyError(uint16_t aErrorCode
) {
927 if (!WindowOwnerStillExists()) {
932 mozilla::Telemetry::Accumulate(mozilla::Telemetry::GEOLOCATION_ERROR
, true);
934 for (uint32_t i
= mPendingCallbacks
.Length(); i
> 0; i
--) {
935 RefPtr
<nsGeolocationRequest
> request
= mPendingCallbacks
[i
- 1];
936 request
->NotifyErrorAndShutdown(aErrorCode
);
937 // NotifyErrorAndShutdown() removes the request from the array
940 // notify everyone that is watching
941 for (uint32_t i
= 0; i
< mWatchingCallbacks
.Length(); i
++) {
942 RefPtr
<nsGeolocationRequest
> request
= mWatchingCallbacks
[i
];
943 request
->NotifyErrorAndShutdown(aErrorCode
);
949 bool Geolocation::IsFullyActiveOrChrome() {
950 // For regular content window, only allow this proceed if the window is "fully
952 if (nsPIDOMWindowInner
* window
= this->GetParentObject()) {
953 return window
->IsFullyActive();
955 // Calls coming from chrome code don't have window, so we can proceed.
959 bool Geolocation::IsAlreadyCleared(nsGeolocationRequest
* aRequest
) {
960 for (uint32_t i
= 0, length
= mClearedWatchIDs
.Length(); i
< length
; ++i
) {
961 if (mClearedWatchIDs
[i
] == aRequest
->WatchId()) {
969 bool Geolocation::ShouldBlockInsecureRequests() const {
970 if (Preferences::GetBool(PREF_GEO_SECURITY_ALLOWINSECURE
, false)) {
974 nsCOMPtr
<nsPIDOMWindowInner
> win
= do_QueryReferent(mOwner
);
979 nsCOMPtr
<Document
> doc
= win
->GetDoc();
984 if (!nsGlobalWindowInner::Cast(win
)->IsSecureContext()) {
985 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag
, "DOM"_ns
, doc
,
986 nsContentUtils::eDOM_PROPERTIES
,
987 "GeolocationInsecureRequestIsForbidden");
994 bool Geolocation::ClearPendingRequest(nsGeolocationRequest
* aRequest
) {
995 if (aRequest
->IsWatch() && this->IsAlreadyCleared(aRequest
)) {
996 this->NotifyAllowedRequest(aRequest
);
997 this->ClearWatch(aRequest
->WatchId());
1004 void Geolocation::GetCurrentPosition(PositionCallback
& aCallback
,
1005 PositionErrorCallback
* aErrorCallback
,
1006 const PositionOptions
& aOptions
,
1007 CallerType aCallerType
, ErrorResult
& aRv
) {
1008 nsresult rv
= GetCurrentPosition(
1009 GeoPositionCallback(&aCallback
), GeoPositionErrorCallback(aErrorCallback
),
1010 CreatePositionOptionsCopy(aOptions
), aCallerType
);
1012 if (NS_FAILED(rv
)) {
1017 static nsIEventTarget
* MainThreadTarget(Geolocation
* geo
) {
1018 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryReferent(geo
->GetOwner());
1020 return GetMainThreadSerialEventTarget();
1022 return nsGlobalWindowInner::Cast(window
)->EventTargetFor(
1023 mozilla::TaskCategory::Other
);
1026 nsresult
Geolocation::GetCurrentPosition(GeoPositionCallback callback
,
1027 GeoPositionErrorCallback errorCallback
,
1028 UniquePtr
<PositionOptions
>&& options
,
1029 CallerType aCallerType
) {
1030 if (!IsFullyActiveOrChrome()) {
1031 RefPtr
<GeolocationPositionError
> positionError
=
1032 new GeolocationPositionError(
1033 this, GeolocationPositionError_Binding::POSITION_UNAVAILABLE
);
1034 positionError
->NotifyCallback(errorCallback
);
1038 if (mPendingCallbacks
.Length() > MAX_GEO_REQUESTS_PER_WINDOW
) {
1039 return NS_ERROR_NOT_AVAILABLE
;
1042 // After this we hand over ownership of options to our nsGeolocationRequest.
1044 nsIEventTarget
* target
= MainThreadTarget(this);
1045 RefPtr
<nsGeolocationRequest
> request
= new nsGeolocationRequest(
1046 this, std::move(callback
), std::move(errorCallback
), std::move(options
),
1049 if (!StaticPrefs::geo_enabled() || ShouldBlockInsecureRequests() ||
1050 !request
->CheckPermissionDelegate()) {
1051 request
->RequestDelayedTask(target
,
1052 nsGeolocationRequest::DelayedTaskType::Deny
);
1056 if (!mOwner
&& aCallerType
!= CallerType::System
) {
1057 return NS_ERROR_FAILURE
;
1061 if (!RegisterRequestWithPrompt(request
)) {
1062 return NS_ERROR_NOT_AVAILABLE
;
1068 if (aCallerType
!= CallerType::System
) {
1069 return NS_ERROR_FAILURE
;
1072 request
->RequestDelayedTask(target
,
1073 nsGeolocationRequest::DelayedTaskType::Allow
);
1078 int32_t Geolocation::WatchPosition(PositionCallback
& aCallback
,
1079 PositionErrorCallback
* aErrorCallback
,
1080 const PositionOptions
& aOptions
,
1081 CallerType aCallerType
, ErrorResult
& aRv
) {
1082 return WatchPosition(GeoPositionCallback(&aCallback
),
1083 GeoPositionErrorCallback(aErrorCallback
),
1084 CreatePositionOptionsCopy(aOptions
), aCallerType
, aRv
);
1087 int32_t Geolocation::WatchPosition(
1088 nsIDOMGeoPositionCallback
* aCallback
,
1089 nsIDOMGeoPositionErrorCallback
* aErrorCallback
,
1090 UniquePtr
<PositionOptions
>&& aOptions
) {
1091 MOZ_ASSERT(aCallback
);
1093 return WatchPosition(GeoPositionCallback(aCallback
),
1094 GeoPositionErrorCallback(aErrorCallback
),
1095 std::move(aOptions
), CallerType::System
, IgnoreErrors());
1098 // On errors we return 0 because that's not a valid watch id and will
1099 // get ignored in clearWatch.
1100 int32_t Geolocation::WatchPosition(GeoPositionCallback aCallback
,
1101 GeoPositionErrorCallback aErrorCallback
,
1102 UniquePtr
<PositionOptions
>&& aOptions
,
1103 CallerType aCallerType
, ErrorResult
& aRv
) {
1104 if (!IsFullyActiveOrChrome()) {
1105 RefPtr
<GeolocationPositionError
> positionError
=
1106 new GeolocationPositionError(
1107 this, GeolocationPositionError_Binding::POSITION_UNAVAILABLE
);
1108 positionError
->NotifyCallback(aErrorCallback
);
1112 if (mWatchingCallbacks
.Length() > MAX_GEO_REQUESTS_PER_WINDOW
) {
1113 aRv
.Throw(NS_ERROR_NOT_AVAILABLE
);
1118 int32_t watchId
= mLastWatchId
++;
1120 nsIEventTarget
* target
= MainThreadTarget(this);
1121 RefPtr
<nsGeolocationRequest
> request
= new nsGeolocationRequest(
1122 this, std::move(aCallback
), std::move(aErrorCallback
),
1123 std::move(aOptions
), target
, true, watchId
);
1125 if (!StaticPrefs::geo_enabled() || ShouldBlockInsecureRequests() ||
1126 !request
->CheckPermissionDelegate()) {
1127 request
->RequestDelayedTask(target
,
1128 nsGeolocationRequest::DelayedTaskType::Deny
);
1132 if (!mOwner
&& aCallerType
!= CallerType::System
) {
1133 aRv
.Throw(NS_ERROR_FAILURE
);
1138 if (!RegisterRequestWithPrompt(request
)) {
1139 aRv
.Throw(NS_ERROR_NOT_AVAILABLE
);
1146 if (aCallerType
!= CallerType::System
) {
1147 aRv
.Throw(NS_ERROR_FAILURE
);
1151 request
->Allow(JS::UndefinedHandleValue
);
1155 void Geolocation::ClearWatch(int32_t aWatchId
) {
1160 if (!mClearedWatchIDs
.Contains(aWatchId
)) {
1161 mClearedWatchIDs
.AppendElement(aWatchId
);
1164 for (uint32_t i
= 0, length
= mWatchingCallbacks
.Length(); i
< length
; ++i
) {
1165 if (mWatchingCallbacks
[i
]->WatchId() == aWatchId
) {
1166 mWatchingCallbacks
[i
]->Shutdown();
1167 RemoveRequest(mWatchingCallbacks
[i
]);
1168 mClearedWatchIDs
.RemoveElement(aWatchId
);
1173 // make sure we also search through the pending requests lists for
1174 // watches to clear...
1175 for (uint32_t i
= 0, length
= mPendingRequests
.Length(); i
< length
; ++i
) {
1176 if (mPendingRequests
[i
]->IsWatch() &&
1177 (mPendingRequests
[i
]->WatchId() == aWatchId
)) {
1178 mPendingRequests
[i
]->Shutdown();
1179 mPendingRequests
.RemoveElementAt(i
);
1185 bool Geolocation::WindowOwnerStillExists() {
1186 // an owner was never set when Geolocation
1187 // was created, which means that this object
1188 // is being used without a window.
1189 if (mOwner
== nullptr) {
1193 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryReferent(mOwner
);
1196 nsPIDOMWindowOuter
* outer
= window
->GetOuterWindow();
1197 if (!outer
|| outer
->GetCurrentInnerWindow() != window
|| outer
->Closed()) {
1205 void Geolocation::NotifyAllowedRequest(nsGeolocationRequest
* aRequest
) {
1206 if (aRequest
->IsWatch()) {
1207 mWatchingCallbacks
.AppendElement(aRequest
);
1209 mPendingCallbacks
.AppendElement(aRequest
);
1213 bool Geolocation::RegisterRequestWithPrompt(nsGeolocationRequest
* request
) {
1214 nsIEventTarget
* target
= MainThreadTarget(this);
1215 ContentPermissionRequestBase::PromptResult pr
= request
->CheckPromptPrefs();
1216 if (pr
== ContentPermissionRequestBase::PromptResult::Granted
) {
1217 request
->RequestDelayedTask(target
,
1218 nsGeolocationRequest::DelayedTaskType::Allow
);
1221 if (pr
== ContentPermissionRequestBase::PromptResult::Denied
) {
1222 request
->RequestDelayedTask(target
,
1223 nsGeolocationRequest::DelayedTaskType::Deny
);
1227 request
->RequestDelayedTask(target
,
1228 nsGeolocationRequest::DelayedTaskType::Request
);
1232 JSObject
* Geolocation::WrapObject(JSContext
* aCtx
,
1233 JS::Handle
<JSObject
*> aGivenProto
) {
1234 return Geolocation_Binding::Wrap(aCtx
, this, aGivenProto
);