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 "nsGlobalWindowInner.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"
60 // Some limit to the number of get or watch geolocation requests
61 // that a window can make.
62 #define MAX_GEO_REQUESTS_PER_WINDOW 1500
64 // This preference allows to override the "secure context" by
66 #define PREF_GEO_SECURITY_ALLOWINSECURE "geo.security.allowinsecure"
68 using mozilla::Unused
; // <snicker>
69 using namespace mozilla
;
70 using namespace mozilla::dom
;
72 class nsGeolocationRequest final
: public ContentPermissionRequestBase
,
73 public nsIGeolocationUpdate
,
74 public SupportsWeakPtr
{
76 NS_DECL_ISUPPORTS_INHERITED
77 NS_DECL_NSIGEOLOCATIONUPDATE
79 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsGeolocationRequest
,
80 ContentPermissionRequestBase
)
82 nsGeolocationRequest(Geolocation
* aLocator
, GeoPositionCallback aCallback
,
83 GeoPositionErrorCallback aErrorCallback
,
84 UniquePtr
<PositionOptions
>&& aOptions
,
85 nsIEventTarget
* aMainThreadSerialEventTarget
,
86 bool aWatchPositionRequest
= false,
87 int32_t aWatchId
= 0);
89 // nsIContentPermissionRequest
90 MOZ_CAN_RUN_SCRIPT NS_IMETHOD
Cancel(void) override
;
91 MOZ_CAN_RUN_SCRIPT NS_IMETHOD
Allow(JS::Handle
<JS::Value
> choices
) override
;
95 // MOZ_CAN_RUN_SCRIPT_BOUNDARY is OK here because we're always called from a
96 // runnable. Ideally nsIRunnable::Run and its overloads would just be
97 // MOZ_CAN_RUN_SCRIPT and then we could be too...
98 MOZ_CAN_RUN_SCRIPT_BOUNDARY
99 void SendLocation(nsIDOMGeoPosition
* aLocation
);
100 bool WantsHighAccuracy() {
101 return !mShutdown
&& mOptions
&& mOptions
->mEnableHighAccuracy
;
103 void SetTimeoutTimer();
104 void StopTimeoutTimer();
106 void NotifyErrorAndShutdown(uint16_t);
107 using ContentPermissionRequestBase::GetPrincipal
;
108 nsIPrincipal
* GetPrincipal();
110 bool IsWatch() { return mIsWatchPositionRequest
; }
111 int32_t WatchId() { return mWatchId
; }
114 virtual ~nsGeolocationRequest();
116 class TimerCallbackHolder final
: public nsITimerCallback
, public nsINamed
{
119 NS_DECL_NSITIMERCALLBACK
121 explicit TimerCallbackHolder(nsGeolocationRequest
* aRequest
)
122 : mRequest(aRequest
) {}
124 NS_IMETHOD
GetName(nsACString
& aName
) override
{
125 aName
.AssignLiteral("nsGeolocationRequest::TimerCallbackHolder");
130 ~TimerCallbackHolder() = default;
131 WeakPtr
<nsGeolocationRequest
> mRequest
;
134 // Only called from a timer, so MOZ_CAN_RUN_SCRIPT_BOUNDARY ok for now.
135 MOZ_CAN_RUN_SCRIPT_BOUNDARY
void Notify();
137 bool mIsWatchPositionRequest
;
139 nsCOMPtr
<nsITimer
> mTimeoutTimer
;
140 GeoPositionCallback mCallback
;
141 GeoPositionErrorCallback mErrorCallback
;
142 UniquePtr
<PositionOptions
> mOptions
;
144 RefPtr
<Geolocation
> mLocator
;
148 nsCOMPtr
<nsIEventTarget
> mMainThreadSerialEventTarget
;
151 static UniquePtr
<PositionOptions
> CreatePositionOptionsCopy(
152 const PositionOptions
& aOptions
) {
153 UniquePtr
<PositionOptions
> geoOptions
= MakeUnique
<PositionOptions
>();
155 geoOptions
->mEnableHighAccuracy
= aOptions
.mEnableHighAccuracy
;
156 geoOptions
->mMaximumAge
= aOptions
.mMaximumAge
;
157 geoOptions
->mTimeout
= aOptions
.mTimeout
;
162 class RequestSendLocationEvent
: public Runnable
{
164 RequestSendLocationEvent(nsIDOMGeoPosition
* aPosition
,
165 nsGeolocationRequest
* aRequest
)
166 : mozilla::Runnable("RequestSendLocationEvent"),
167 mPosition(aPosition
),
168 mRequest(aRequest
) {}
170 NS_IMETHOD
Run() override
{
171 mRequest
->SendLocation(mPosition
);
176 nsCOMPtr
<nsIDOMGeoPosition
> mPosition
;
177 RefPtr
<nsGeolocationRequest
> mRequest
;
178 RefPtr
<Geolocation
> mLocator
;
181 ////////////////////////////////////////////////////
182 // nsGeolocationRequest
183 ////////////////////////////////////////////////////
185 static nsPIDOMWindowInner
* ConvertWeakReferenceToWindow(
186 nsIWeakReference
* aWeakPtr
) {
187 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryReferent(aWeakPtr
);
188 // This isn't usually safe, but here we're just extracting a raw pointer in
189 // order to pass it to a base class constructor which will in turn convert it
190 // into a strong pointer for us.
191 nsPIDOMWindowInner
* raw
= window
.get();
195 nsGeolocationRequest::nsGeolocationRequest(
196 Geolocation
* aLocator
, GeoPositionCallback aCallback
,
197 GeoPositionErrorCallback aErrorCallback
,
198 UniquePtr
<PositionOptions
>&& aOptions
,
199 nsIEventTarget
* aMainThreadSerialEventTarget
, bool aWatchPositionRequest
,
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 mMainThreadSerialEventTarget(aMainThreadSerialEventTarget
) {}
214 nsGeolocationRequest::~nsGeolocationRequest() { StopTimeoutTimer(); }
216 NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(nsGeolocationRequest
,
217 ContentPermissionRequestBase
,
218 nsIGeolocationUpdate
)
220 NS_IMPL_ADDREF_INHERITED(nsGeolocationRequest
, ContentPermissionRequestBase
)
221 NS_IMPL_RELEASE_INHERITED(nsGeolocationRequest
, ContentPermissionRequestBase
)
222 NS_IMPL_CYCLE_COLLECTION_WEAK_PTR_INHERITED(nsGeolocationRequest
,
223 ContentPermissionRequestBase
,
224 mCallback
, mErrorCallback
, mLocator
)
226 void nsGeolocationRequest::Notify() {
228 NotifyErrorAndShutdown(GeolocationPositionError_Binding::TIMEOUT
);
231 void nsGeolocationRequest::NotifyErrorAndShutdown(uint16_t aErrorCode
) {
232 MOZ_ASSERT(!mShutdown
, "timeout after shutdown");
233 if (!mIsWatchPositionRequest
) {
235 mLocator
->RemoveRequest(this);
238 NotifyError(aErrorCode
);
242 nsGeolocationRequest::Cancel() {
243 if (mLocator
->ClearPendingRequest(this)) {
247 NotifyError(GeolocationPositionError_Binding::PERMISSION_DENIED
);
252 nsGeolocationRequest::Allow(JS::Handle
<JS::Value
> aChoices
) {
253 MOZ_ASSERT(aChoices
.isUndefined());
255 if (mLocator
->ClearPendingRequest(this)) {
259 RefPtr
<nsGeolocationService
> gs
=
260 nsGeolocationService::GetGeolocationService();
262 bool canUseCache
= false;
263 CachedPositionAndAccuracy lastPosition
= gs
->GetCachedPosition();
264 if (lastPosition
.position
) {
265 EpochTimeStamp cachedPositionTime_ms
;
266 lastPosition
.position
->GetTimestamp(&cachedPositionTime_ms
);
267 // check to see if we can use a cached value
268 // if the user has specified a maximumAge, return a cached value.
269 if (mOptions
&& mOptions
->mMaximumAge
> 0) {
270 uint32_t maximumAge_ms
= mOptions
->mMaximumAge
;
271 bool isCachedWithinRequestedAccuracy
=
272 WantsHighAccuracy() <= lastPosition
.isHighAccuracy
;
273 bool isCachedWithinRequestedTime
=
274 EpochTimeStamp(PR_Now() / PR_USEC_PER_MSEC
- maximumAge_ms
) <=
275 cachedPositionTime_ms
;
277 isCachedWithinRequestedAccuracy
&& isCachedWithinRequestedTime
;
281 gs
->UpdateAccuracy(WantsHighAccuracy());
283 // okay, we can return a cached position
284 // getCurrentPosition requests serviced by the cache
285 // will now be owned by the RequestSendLocationEvent
286 Update(lastPosition
.position
);
288 // After Update is called, getCurrentPosition finishes it's job.
289 if (!mIsWatchPositionRequest
) {
294 // if it is not a watch request and timeout is 0,
295 // invoke the errorCallback (if present) with TIMEOUT code
296 if (mOptions
&& mOptions
->mTimeout
== 0 && !mIsWatchPositionRequest
) {
297 NotifyError(GeolocationPositionError_Binding::TIMEOUT
);
302 // Non-cached location request
303 bool allowedRequest
= mIsWatchPositionRequest
|| !canUseCache
;
304 if (allowedRequest
) {
305 // let the locator know we're pending
306 // we will now be owned by the locator
307 mLocator
->NotifyAllowedRequest(this);
310 // Kick off the geo device, if it isn't already running
311 nsresult rv
= gs
->StartDevice();
314 if (allowedRequest
) {
315 mLocator
->RemoveRequest(this);
317 // Location provider error
318 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE
);
327 void nsGeolocationRequest::SetTimeoutTimer() {
328 MOZ_ASSERT(!mShutdown
, "set timeout after shutdown");
332 if (mOptions
&& mOptions
->mTimeout
!= 0 && mOptions
->mTimeout
!= 0x7fffffff) {
333 RefPtr
<TimerCallbackHolder
> holder
= new TimerCallbackHolder(this);
334 NS_NewTimerWithCallback(getter_AddRefs(mTimeoutTimer
), holder
,
335 mOptions
->mTimeout
, nsITimer::TYPE_ONE_SHOT
);
339 void nsGeolocationRequest::StopTimeoutTimer() {
341 mTimeoutTimer
->Cancel();
342 mTimeoutTimer
= nullptr;
346 void nsGeolocationRequest::SendLocation(nsIDOMGeoPosition
* aPosition
) {
348 // Ignore SendLocationEvents issued before we were cleared.
352 if (mOptions
&& mOptions
->mMaximumAge
> 0) {
353 EpochTimeStamp positionTime_ms
;
354 aPosition
->GetTimestamp(&positionTime_ms
);
355 const uint32_t maximumAge_ms
= mOptions
->mMaximumAge
;
356 const bool isTooOld
= EpochTimeStamp(PR_Now() / PR_USEC_PER_MSEC
-
357 maximumAge_ms
) > positionTime_ms
;
363 RefPtr
<mozilla::dom::GeolocationPosition
> wrapped
;
366 nsCOMPtr
<nsIDOMGeoPositionCoords
> coords
;
367 aPosition
->GetCoords(getter_AddRefs(coords
));
369 wrapped
= new mozilla::dom::GeolocationPosition(ToSupports(mLocator
),
375 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE
);
379 if (!mIsWatchPositionRequest
) {
380 // Cancel timer and position updates in case the position
381 // callback spins the event loop
385 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
386 obs
->NotifyObservers(wrapped
, "geolocation-position-events",
387 u
"location-updated");
390 if (mCallback
.HasWebIDLCallback()) {
391 RefPtr
<PositionCallback
> callback
= mCallback
.GetWebIDLCallback();
393 MOZ_ASSERT(callback
);
394 callback
->Call(*wrapped
);
396 nsIDOMGeoPositionCallback
* callback
= mCallback
.GetXPCOMCallback();
397 MOZ_ASSERT(callback
);
398 callback
->HandleEvent(aPosition
);
401 if (mIsWatchPositionRequest
&& !mShutdown
) {
404 MOZ_ASSERT(mShutdown
|| mIsWatchPositionRequest
,
405 "non-shutdown getCurrentPosition request after callback!");
408 nsIPrincipal
* nsGeolocationRequest::GetPrincipal() {
412 return mLocator
->GetPrincipal();
416 nsGeolocationRequest::Update(nsIDOMGeoPosition
* aPosition
) {
417 nsCOMPtr
<nsIRunnable
> ev
= new RequestSendLocationEvent(aPosition
, this);
418 mMainThreadSerialEventTarget
->Dispatch(ev
.forget());
423 nsGeolocationRequest::NotifyError(uint16_t aErrorCode
) {
424 MOZ_ASSERT(NS_IsMainThread());
425 RefPtr
<GeolocationPositionError
> positionError
=
426 new GeolocationPositionError(mLocator
, aErrorCode
);
427 positionError
->NotifyCallback(mErrorCallback
);
431 void nsGeolocationRequest::Shutdown() {
432 MOZ_ASSERT(!mShutdown
, "request shutdown twice");
437 // If there are no other high accuracy requests, the geolocation service will
438 // notify the provider to switch to the default accuracy.
439 if (mOptions
&& mOptions
->mEnableHighAccuracy
) {
440 RefPtr
<nsGeolocationService
> gs
=
441 nsGeolocationService::GetGeolocationService();
443 gs
->UpdateAccuracy();
448 ////////////////////////////////////////////////////
449 // nsGeolocationRequest::TimerCallbackHolder
450 ////////////////////////////////////////////////////
452 NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder
, nsITimerCallback
,
456 nsGeolocationRequest::TimerCallbackHolder::Notify(nsITimer
*) {
457 if (mRequest
&& mRequest
->mLocator
) {
458 RefPtr
<nsGeolocationRequest
> request(mRequest
);
465 ////////////////////////////////////////////////////
466 // nsGeolocationService
467 ////////////////////////////////////////////////////
468 NS_INTERFACE_MAP_BEGIN(nsGeolocationService
)
469 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIGeolocationUpdate
)
470 NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate
)
471 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
474 NS_IMPL_ADDREF(nsGeolocationService
)
475 NS_IMPL_RELEASE(nsGeolocationService
)
477 nsresult
nsGeolocationService::Init() {
478 if (!StaticPrefs::geo_enabled()) {
479 return NS_ERROR_FAILURE
;
482 if (XRE_IsContentProcess()) {
486 // geolocation service can be enabled -> now register observer
487 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
489 return NS_ERROR_FAILURE
;
492 obs
->AddObserver(this, "xpcom-shutdown", false);
494 #ifdef MOZ_WIDGET_ANDROID
495 mProvider
= new AndroidLocationProvider();
498 #ifdef MOZ_WIDGET_GTK
499 # ifdef MOZ_ENABLE_DBUS
500 if (!mProvider
&& widget::ShouldUsePortal(widget::PortalKind::Location
)) {
501 mProvider
= new PortalLocationProvider();
503 // Geoclue includes GPS data so it has higher priority than raw GPSD
504 if (!mProvider
&& StaticPrefs::geo_provider_use_geoclue()) {
505 nsCOMPtr
<nsIGeolocationProvider
> gcProvider
= new GeoclueLocationProvider();
506 // The Startup() method will only succeed if Geoclue is available on D-Bus
507 if (NS_SUCCEEDED(gcProvider
->Startup())) {
508 gcProvider
->Shutdown();
509 mProvider
= std::move(gcProvider
);
513 if (!mProvider
&& Preferences::GetBool("geo.provider.use_gpsd", false)) {
514 mProvider
= new GpsdLocationProvider();
520 #ifdef MOZ_WIDGET_COCOA
521 if (Preferences::GetBool("geo.provider.use_corelocation", true)) {
522 mProvider
= new CoreLocationLocationProvider();
527 if (Preferences::GetBool("geo.provider.ms-windows-location", false)) {
528 mProvider
= new WindowsLocationProvider();
532 if (Preferences::GetBool("geo.provider.use_mls", false)) {
533 mProvider
= do_CreateInstance("@mozilla.org/geolocation/mls-provider;1");
536 // Override platform-specific providers with the default (network)
537 // provider while testing. Our tests are currently not meant to exercise
538 // the provider, and some tests rely on the network provider being used.
539 // "geo.provider.testing" is always set for all plain and browser chrome
540 // mochitests, and also for xpcshell tests.
541 if (!mProvider
|| Preferences::GetBool("geo.provider.testing", false)) {
542 nsCOMPtr
<nsIGeolocationProvider
> geoTestProvider
=
543 do_GetService(NS_GEOLOCATION_PROVIDER_CONTRACTID
);
545 if (geoTestProvider
) {
546 mProvider
= geoTestProvider
;
553 nsGeolocationService::~nsGeolocationService() = default;
556 nsGeolocationService::Observe(nsISupports
* aSubject
, const char* aTopic
,
557 const char16_t
* aData
) {
558 if (!strcmp("xpcom-shutdown", aTopic
)) {
559 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
561 obs
->RemoveObserver(this, "xpcom-shutdown");
564 for (uint32_t i
= 0; i
< mGeolocators
.Length(); i
++) {
565 mGeolocators
[i
]->Shutdown();
572 if (!strcmp("timer-callback", aTopic
)) {
573 // decide if we can close down the service.
574 for (uint32_t i
= 0; i
< mGeolocators
.Length(); i
++)
575 if (mGeolocators
[i
]->HasActiveCallbacks()) {
576 SetDisconnectTimer();
586 return NS_ERROR_FAILURE
;
590 nsGeolocationService::Update(nsIDOMGeoPosition
* aSomewhere
) {
592 SetCachedPosition(aSomewhere
);
595 for (uint32_t i
= 0; i
< mGeolocators
.Length(); i
++) {
596 mGeolocators
[i
]->Update(aSomewhere
);
603 nsGeolocationService::NotifyError(uint16_t aErrorCode
) {
604 // nsTArray doesn't have a constructors that takes a different-type TArray.
605 nsTArray
<RefPtr
<Geolocation
>> geolocators
;
606 geolocators
.AppendElements(mGeolocators
);
607 for (uint32_t i
= 0; i
< geolocators
.Length(); i
++) {
608 // MOZ_KnownLive because the stack array above keeps it alive.
609 MOZ_KnownLive(geolocators
[i
])->NotifyError(aErrorCode
);
614 void nsGeolocationService::SetCachedPosition(nsIDOMGeoPosition
* aPosition
) {
615 mLastPosition
.position
= aPosition
;
616 mLastPosition
.isHighAccuracy
= mHigherAccuracy
;
619 CachedPositionAndAccuracy
nsGeolocationService::GetCachedPosition() {
620 return mLastPosition
;
623 nsresult
nsGeolocationService::StartDevice() {
624 if (!StaticPrefs::geo_enabled()) {
625 return NS_ERROR_NOT_AVAILABLE
;
628 // We do not want to keep the geolocation devices online
630 // Close them down after a reasonable period of inactivivity.
631 SetDisconnectTimer();
633 if (XRE_IsContentProcess()) {
634 ContentChild
* cpc
= ContentChild::GetSingleton();
635 cpc
->SendAddGeolocationListener(HighAccuracyRequested());
640 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
642 return NS_ERROR_FAILURE
;
646 return NS_ERROR_FAILURE
;
651 if (NS_FAILED(rv
= mProvider
->Startup()) ||
652 NS_FAILED(rv
= mProvider
->Watch(this))) {
653 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE
);
657 obs
->NotifyObservers(mProvider
, "geolocation-device-events", u
"starting");
662 void nsGeolocationService::SetDisconnectTimer() {
663 if (!mDisconnectTimer
) {
664 mDisconnectTimer
= NS_NewTimer();
666 mDisconnectTimer
->Cancel();
669 mDisconnectTimer
->Init(this, StaticPrefs::geo_timeout(),
670 nsITimer::TYPE_ONE_SHOT
);
673 bool nsGeolocationService::HighAccuracyRequested() {
674 for (uint32_t i
= 0; i
< mGeolocators
.Length(); i
++) {
675 if (mGeolocators
[i
]->HighAccuracyRequested()) {
683 void nsGeolocationService::UpdateAccuracy(bool aForceHigh
) {
684 bool highRequired
= aForceHigh
|| HighAccuracyRequested();
686 if (XRE_IsContentProcess()) {
687 ContentChild
* cpc
= ContentChild::GetSingleton();
688 if (cpc
->IsAlive()) {
689 cpc
->SendSetGeolocationHigherAccuracy(highRequired
);
695 mProvider
->SetHighAccuracy(!mHigherAccuracy
&& highRequired
);
696 mHigherAccuracy
= highRequired
;
699 void nsGeolocationService::StopDevice() {
700 if (mDisconnectTimer
) {
701 mDisconnectTimer
->Cancel();
702 mDisconnectTimer
= nullptr;
705 if (XRE_IsContentProcess()) {
706 ContentChild
* cpc
= ContentChild::GetSingleton();
707 cpc
->SendRemoveGeolocationListener();
709 return; // bail early
712 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
721 mHigherAccuracy
= false;
723 mProvider
->Shutdown();
724 obs
->NotifyObservers(mProvider
, "geolocation-device-events", u
"shutdown");
727 StaticRefPtr
<nsGeolocationService
> nsGeolocationService::sService
;
729 already_AddRefed
<nsGeolocationService
>
730 nsGeolocationService::GetGeolocationService() {
731 RefPtr
<nsGeolocationService
> result
;
732 if (nsGeolocationService::sService
) {
733 result
= nsGeolocationService::sService
;
735 return result
.forget();
738 result
= new nsGeolocationService();
739 if (NS_FAILED(result
->Init())) {
743 ClearOnShutdown(&nsGeolocationService::sService
);
744 nsGeolocationService::sService
= result
;
745 return result
.forget();
748 void nsGeolocationService::AddLocator(Geolocation
* aLocator
) {
749 mGeolocators
.AppendElement(aLocator
);
752 void nsGeolocationService::RemoveLocator(Geolocation
* aLocator
) {
753 mGeolocators
.RemoveElement(aLocator
);
756 ////////////////////////////////////////////////////
758 ////////////////////////////////////////////////////
760 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Geolocation
)
761 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
762 NS_INTERFACE_MAP_ENTRY(nsISupports
)
763 NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate
)
766 NS_IMPL_CYCLE_COLLECTING_ADDREF(Geolocation
)
767 NS_IMPL_CYCLE_COLLECTING_RELEASE(Geolocation
)
769 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Geolocation
, mPendingCallbacks
,
770 mWatchingCallbacks
, mPendingRequests
)
772 Geolocation::Geolocation()
773 : mProtocolType(ProtocolType::OTHER
), mLastWatchId(1) {}
775 Geolocation::~Geolocation() {
781 StaticRefPtr
<Geolocation
> Geolocation::sNonWindowSingleton
;
783 already_AddRefed
<Geolocation
> Geolocation::NonWindowSingleton() {
784 if (sNonWindowSingleton
) {
785 return do_AddRef(sNonWindowSingleton
);
788 RefPtr
<Geolocation
> result
= new Geolocation();
789 DebugOnly
<nsresult
> rv
= result
->Init();
790 MOZ_ASSERT(NS_SUCCEEDED(rv
), "How can this fail?");
792 ClearOnShutdown(&sNonWindowSingleton
);
793 sNonWindowSingleton
= result
;
794 return result
.forget();
797 nsresult
Geolocation::Init(nsPIDOMWindowInner
* aContentDom
) {
798 // Remember the window
800 mOwner
= do_GetWeakReference(aContentDom
);
802 return NS_ERROR_FAILURE
;
805 // Grab the principal of the document
806 nsCOMPtr
<Document
> doc
= aContentDom
->GetDoc();
808 return NS_ERROR_FAILURE
;
811 mPrincipal
= doc
->NodePrincipal();
812 // Store the protocol to send via telemetry later.
813 if (mPrincipal
->SchemeIs("http")) {
814 mProtocolType
= ProtocolType::HTTP
;
815 } else if (mPrincipal
->SchemeIs("https")) {
816 mProtocolType
= ProtocolType::HTTPS
;
820 // If no aContentDom was passed into us, we are being used
821 // by chrome/c++ and have no mOwner, no mPrincipal, and no need
823 mService
= nsGeolocationService::GetGeolocationService();
825 mService
->AddLocator(this);
831 void Geolocation::Shutdown() {
832 // Release all callbacks
833 mPendingCallbacks
.Clear();
834 mWatchingCallbacks
.Clear();
837 mService
->RemoveLocator(this);
838 mService
->UpdateAccuracy();
842 mPrincipal
= nullptr;
845 nsPIDOMWindowInner
* Geolocation::GetParentObject() const {
846 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryReferent(mOwner
);
850 bool Geolocation::HasActiveCallbacks() {
851 return mPendingCallbacks
.Length() || mWatchingCallbacks
.Length();
854 bool Geolocation::HighAccuracyRequested() {
855 for (uint32_t i
= 0; i
< mWatchingCallbacks
.Length(); i
++) {
856 if (mWatchingCallbacks
[i
]->WantsHighAccuracy()) {
861 for (uint32_t i
= 0; i
< mPendingCallbacks
.Length(); i
++) {
862 if (mPendingCallbacks
[i
]->WantsHighAccuracy()) {
870 void Geolocation::RemoveRequest(nsGeolocationRequest
* aRequest
) {
871 bool requestWasKnown
= (mPendingCallbacks
.RemoveElement(aRequest
) !=
872 mWatchingCallbacks
.RemoveElement(aRequest
));
874 Unused
<< requestWasKnown
;
878 Geolocation::Update(nsIDOMGeoPosition
* aSomewhere
) {
879 if (!WindowOwnerStillExists()) {
884 // Don't update position if window is not fully active or the document is
885 // hidden. We keep the pending callaback and watchers waiting for the next
887 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryReferent(this->GetOwner());
889 nsCOMPtr
<Document
> document
= window
->GetDoc();
890 bool isHidden
= document
&& document
->Hidden();
891 if (isHidden
|| !window
->IsFullyActive()) {
897 nsCOMPtr
<nsIDOMGeoPositionCoords
> coords
;
898 aSomewhere
->GetCoords(getter_AddRefs(coords
));
900 double accuracy
= -1;
901 coords
->GetAccuracy(&accuracy
);
902 mozilla::Telemetry::Accumulate(
903 mozilla::Telemetry::GEOLOCATION_ACCURACY_EXPONENTIAL
, accuracy
);
907 for (uint32_t i
= mPendingCallbacks
.Length(); i
> 0; i
--) {
908 mPendingCallbacks
[i
- 1]->Update(aSomewhere
);
909 RemoveRequest(mPendingCallbacks
[i
- 1]);
912 // notify everyone that is watching
913 for (uint32_t i
= 0; i
< mWatchingCallbacks
.Length(); i
++) {
914 mWatchingCallbacks
[i
]->Update(aSomewhere
);
921 Geolocation::NotifyError(uint16_t aErrorCode
) {
922 if (!WindowOwnerStillExists()) {
927 mozilla::Telemetry::Accumulate(mozilla::Telemetry::GEOLOCATION_ERROR
, true);
929 for (uint32_t i
= mPendingCallbacks
.Length(); i
> 0; i
--) {
930 RefPtr
<nsGeolocationRequest
> request
= mPendingCallbacks
[i
- 1];
931 request
->NotifyErrorAndShutdown(aErrorCode
);
932 // NotifyErrorAndShutdown() removes the request from the array
935 // notify everyone that is watching
936 for (uint32_t i
= 0; i
< mWatchingCallbacks
.Length(); i
++) {
937 RefPtr
<nsGeolocationRequest
> request
= mWatchingCallbacks
[i
];
938 request
->NotifyErrorAndShutdown(aErrorCode
);
944 bool Geolocation::IsFullyActiveOrChrome() {
945 // For regular content window, only allow this proceed if the window is "fully
947 if (nsPIDOMWindowInner
* window
= this->GetParentObject()) {
948 return window
->IsFullyActive();
950 // Calls coming from chrome code don't have window, so we can proceed.
954 bool Geolocation::IsAlreadyCleared(nsGeolocationRequest
* aRequest
) {
955 for (uint32_t i
= 0, length
= mClearedWatchIDs
.Length(); i
< length
; ++i
) {
956 if (mClearedWatchIDs
[i
] == aRequest
->WatchId()) {
964 bool Geolocation::ShouldBlockInsecureRequests() const {
965 if (Preferences::GetBool(PREF_GEO_SECURITY_ALLOWINSECURE
, false)) {
969 nsCOMPtr
<nsPIDOMWindowInner
> win
= do_QueryReferent(mOwner
);
974 nsCOMPtr
<Document
> doc
= win
->GetDoc();
979 if (!nsGlobalWindowInner::Cast(win
)->IsSecureContext()) {
980 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag
, "DOM"_ns
, doc
,
981 nsContentUtils::eDOM_PROPERTIES
,
982 "GeolocationInsecureRequestIsForbidden");
989 bool Geolocation::ClearPendingRequest(nsGeolocationRequest
* aRequest
) {
990 if (aRequest
->IsWatch() && this->IsAlreadyCleared(aRequest
)) {
991 this->NotifyAllowedRequest(aRequest
);
992 this->ClearWatch(aRequest
->WatchId());
999 void Geolocation::GetCurrentPosition(PositionCallback
& aCallback
,
1000 PositionErrorCallback
* aErrorCallback
,
1001 const PositionOptions
& aOptions
,
1002 CallerType aCallerType
, ErrorResult
& aRv
) {
1003 nsresult rv
= GetCurrentPosition(
1004 GeoPositionCallback(&aCallback
), GeoPositionErrorCallback(aErrorCallback
),
1005 CreatePositionOptionsCopy(aOptions
), aCallerType
);
1007 if (NS_FAILED(rv
)) {
1012 nsresult
Geolocation::GetCurrentPosition(GeoPositionCallback callback
,
1013 GeoPositionErrorCallback errorCallback
,
1014 UniquePtr
<PositionOptions
>&& options
,
1015 CallerType aCallerType
) {
1016 if (!IsFullyActiveOrChrome()) {
1017 RefPtr
<GeolocationPositionError
> positionError
=
1018 new GeolocationPositionError(
1019 this, GeolocationPositionError_Binding::POSITION_UNAVAILABLE
);
1020 positionError
->NotifyCallback(errorCallback
);
1024 if (mPendingCallbacks
.Length() > MAX_GEO_REQUESTS_PER_WINDOW
) {
1025 return NS_ERROR_NOT_AVAILABLE
;
1028 // After this we hand over ownership of options to our nsGeolocationRequest.
1030 nsIEventTarget
* target
= GetMainThreadSerialEventTarget();
1031 RefPtr
<nsGeolocationRequest
> request
= new nsGeolocationRequest(
1032 this, std::move(callback
), std::move(errorCallback
), std::move(options
),
1035 if (!StaticPrefs::geo_enabled() || ShouldBlockInsecureRequests() ||
1036 !request
->CheckPermissionDelegate()) {
1037 request
->RequestDelayedTask(target
,
1038 nsGeolocationRequest::DelayedTaskType::Deny
);
1042 if (!mOwner
&& aCallerType
!= CallerType::System
) {
1043 return NS_ERROR_FAILURE
;
1047 if (!RegisterRequestWithPrompt(request
)) {
1048 return NS_ERROR_NOT_AVAILABLE
;
1054 if (aCallerType
!= CallerType::System
) {
1055 return NS_ERROR_FAILURE
;
1058 request
->RequestDelayedTask(target
,
1059 nsGeolocationRequest::DelayedTaskType::Allow
);
1064 int32_t Geolocation::WatchPosition(PositionCallback
& aCallback
,
1065 PositionErrorCallback
* aErrorCallback
,
1066 const PositionOptions
& aOptions
,
1067 CallerType aCallerType
, ErrorResult
& aRv
) {
1068 return WatchPosition(GeoPositionCallback(&aCallback
),
1069 GeoPositionErrorCallback(aErrorCallback
),
1070 CreatePositionOptionsCopy(aOptions
), aCallerType
, aRv
);
1073 int32_t Geolocation::WatchPosition(
1074 nsIDOMGeoPositionCallback
* aCallback
,
1075 nsIDOMGeoPositionErrorCallback
* aErrorCallback
,
1076 UniquePtr
<PositionOptions
>&& aOptions
) {
1077 MOZ_ASSERT(aCallback
);
1079 return WatchPosition(GeoPositionCallback(aCallback
),
1080 GeoPositionErrorCallback(aErrorCallback
),
1081 std::move(aOptions
), CallerType::System
, IgnoreErrors());
1084 // On errors we return 0 because that's not a valid watch id and will
1085 // get ignored in clearWatch.
1086 int32_t Geolocation::WatchPosition(GeoPositionCallback aCallback
,
1087 GeoPositionErrorCallback aErrorCallback
,
1088 UniquePtr
<PositionOptions
>&& aOptions
,
1089 CallerType aCallerType
, ErrorResult
& aRv
) {
1090 if (!IsFullyActiveOrChrome()) {
1091 RefPtr
<GeolocationPositionError
> positionError
=
1092 new GeolocationPositionError(
1093 this, GeolocationPositionError_Binding::POSITION_UNAVAILABLE
);
1094 positionError
->NotifyCallback(aErrorCallback
);
1098 if (mWatchingCallbacks
.Length() > MAX_GEO_REQUESTS_PER_WINDOW
) {
1099 aRv
.Throw(NS_ERROR_NOT_AVAILABLE
);
1104 int32_t watchId
= mLastWatchId
++;
1106 nsIEventTarget
* target
= GetMainThreadSerialEventTarget();
1107 RefPtr
<nsGeolocationRequest
> request
= new nsGeolocationRequest(
1108 this, std::move(aCallback
), std::move(aErrorCallback
),
1109 std::move(aOptions
), target
, true, watchId
);
1111 if (!StaticPrefs::geo_enabled() || ShouldBlockInsecureRequests() ||
1112 !request
->CheckPermissionDelegate()) {
1113 request
->RequestDelayedTask(target
,
1114 nsGeolocationRequest::DelayedTaskType::Deny
);
1118 if (!mOwner
&& aCallerType
!= CallerType::System
) {
1119 aRv
.Throw(NS_ERROR_FAILURE
);
1124 if (!RegisterRequestWithPrompt(request
)) {
1125 aRv
.Throw(NS_ERROR_NOT_AVAILABLE
);
1132 if (aCallerType
!= CallerType::System
) {
1133 aRv
.Throw(NS_ERROR_FAILURE
);
1137 request
->Allow(JS::UndefinedHandleValue
);
1141 void Geolocation::ClearWatch(int32_t aWatchId
) {
1146 if (!mClearedWatchIDs
.Contains(aWatchId
)) {
1147 mClearedWatchIDs
.AppendElement(aWatchId
);
1150 for (uint32_t i
= 0, length
= mWatchingCallbacks
.Length(); i
< length
; ++i
) {
1151 if (mWatchingCallbacks
[i
]->WatchId() == aWatchId
) {
1152 mWatchingCallbacks
[i
]->Shutdown();
1153 RemoveRequest(mWatchingCallbacks
[i
]);
1154 mClearedWatchIDs
.RemoveElement(aWatchId
);
1159 // make sure we also search through the pending requests lists for
1160 // watches to clear...
1161 for (uint32_t i
= 0, length
= mPendingRequests
.Length(); i
< length
; ++i
) {
1162 if (mPendingRequests
[i
]->IsWatch() &&
1163 (mPendingRequests
[i
]->WatchId() == aWatchId
)) {
1164 mPendingRequests
[i
]->Shutdown();
1165 mPendingRequests
.RemoveElementAt(i
);
1171 bool Geolocation::WindowOwnerStillExists() {
1172 // an owner was never set when Geolocation
1173 // was created, which means that this object
1174 // is being used without a window.
1175 if (mOwner
== nullptr) {
1179 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryReferent(mOwner
);
1182 nsPIDOMWindowOuter
* outer
= window
->GetOuterWindow();
1183 if (!outer
|| outer
->GetCurrentInnerWindow() != window
|| outer
->Closed()) {
1191 void Geolocation::NotifyAllowedRequest(nsGeolocationRequest
* aRequest
) {
1192 if (aRequest
->IsWatch()) {
1193 mWatchingCallbacks
.AppendElement(aRequest
);
1195 mPendingCallbacks
.AppendElement(aRequest
);
1199 bool Geolocation::RegisterRequestWithPrompt(nsGeolocationRequest
* request
) {
1200 nsIEventTarget
* target
= GetMainThreadSerialEventTarget();
1201 ContentPermissionRequestBase::PromptResult pr
= request
->CheckPromptPrefs();
1202 if (pr
== ContentPermissionRequestBase::PromptResult::Granted
) {
1203 request
->RequestDelayedTask(target
,
1204 nsGeolocationRequest::DelayedTaskType::Allow
);
1207 if (pr
== ContentPermissionRequestBase::PromptResult::Denied
) {
1208 request
->RequestDelayedTask(target
,
1209 nsGeolocationRequest::DelayedTaskType::Deny
);
1213 request
->RequestDelayedTask(target
,
1214 nsGeolocationRequest::DelayedTaskType::Request
);
1218 JSObject
* Geolocation::WrapObject(JSContext
* aCtx
,
1219 JS::Handle
<JSObject
*> aGivenProto
) {
1220 return Geolocation_Binding::Wrap(aCtx
, this, aGivenProto
);