Bug 1828523 [wpt PR 39579] - Only restore dialog focus if focus is in the dialog...
[gecko.git] / dom / geolocation / Geolocation.cpp
blobff6fe276e3f5a19e3e22d98c4a38222880797d99
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"
28 #include "nsINamed.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"
36 class nsIPrincipal;
38 #ifdef MOZ_WIDGET_ANDROID
39 # include "AndroidLocationProvider.h"
40 #endif
42 #ifdef MOZ_GPSD
43 # include "GpsdLocationProvider.h"
44 #endif
46 #ifdef MOZ_ENABLE_DBUS
47 # include "mozilla/WidgetUtilsGtk.h"
48 # include "GeoclueLocationProvider.h"
49 # include "PortalLocationProvider.h"
50 #endif
52 #ifdef MOZ_WIDGET_COCOA
53 # include "CoreLocationLocationProvider.h"
54 #endif
56 #ifdef XP_WIN
57 # include "WindowsLocationProvider.h"
58 # include "mozilla/WindowsVersion.h"
59 #endif
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
66 // default policy.
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 {
76 public:
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;
94 void Shutdown();
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();
106 MOZ_CAN_RUN_SCRIPT
107 void NotifyErrorAndShutdown(uint16_t);
108 using ContentPermissionRequestBase::GetPrincipal;
109 nsIPrincipal* GetPrincipal();
111 bool IsWatch() { return mIsWatchPositionRequest; }
112 int32_t WatchId() { return mWatchId; }
114 private:
115 virtual ~nsGeolocationRequest();
117 class TimerCallbackHolder final : public nsITimerCallback, public nsINamed {
118 public:
119 NS_DECL_ISUPPORTS
120 NS_DECL_NSITIMERCALLBACK
122 explicit TimerCallbackHolder(nsGeolocationRequest* aRequest)
123 : mRequest(aRequest) {}
125 NS_IMETHOD GetName(nsACString& aName) override {
126 aName.AssignLiteral("nsGeolocationRequest::TimerCallbackHolder");
127 return NS_OK;
130 private:
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;
147 int32_t mWatchId;
148 bool mShutdown;
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;
160 return geoOptions;
163 class RequestSendLocationEvent : public Runnable {
164 public:
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);
173 return NS_OK;
176 private:
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();
193 return raw;
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,
204 "geolocation"_ns),
205 mIsWatchPositionRequest(aWatchPositionRequest),
206 mCallback(std::move(aCallback)),
207 mErrorCallback(std::move(aErrorCallback)),
208 mOptions(std::move(aOptions)),
209 mLocator(aLocator),
210 mWatchId(aWatchId),
211 mShutdown(false),
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() {
231 SetTimeoutTimer();
232 NotifyErrorAndShutdown(GeolocationPositionError_Binding::TIMEOUT);
235 void nsGeolocationRequest::NotifyErrorAndShutdown(uint16_t aErrorCode) {
236 MOZ_ASSERT(!mShutdown, "timeout after shutdown");
237 if (!mIsWatchPositionRequest) {
238 Shutdown();
239 mLocator->RemoveRequest(this);
242 NotifyError(aErrorCode);
245 NS_IMETHODIMP
246 nsGeolocationRequest::Cancel() {
247 if (mLocator->ClearPendingRequest(this)) {
248 return NS_OK;
251 NotifyError(GeolocationPositionError_Binding::PERMISSION_DENIED);
252 return NS_OK;
255 NS_IMETHODIMP
256 nsGeolocationRequest::Allow(JS::Handle<JS::Value> aChoices) {
257 MOZ_ASSERT(aChoices.isUndefined());
259 if (mLocator->ClearPendingRequest(this)) {
260 return NS_OK;
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;
280 canUseCache =
281 isCachedWithinRequestedAccuracy && isCachedWithinRequestedTime;
285 gs->UpdateAccuracy(WantsHighAccuracy());
286 if (canUseCache) {
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) {
294 return NS_OK;
297 } else {
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);
302 return NS_OK;
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();
317 if (NS_FAILED(rv)) {
318 if (allowedRequest) {
319 mLocator->RemoveRequest(this);
321 // Location provider error
322 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE);
323 return NS_OK;
326 SetTimeoutTimer();
328 return NS_OK;
331 void nsGeolocationRequest::SetTimeoutTimer() {
332 MOZ_ASSERT(!mShutdown, "set timeout after shutdown");
334 StopTimeoutTimer();
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() {
344 if (mTimeoutTimer) {
345 mTimeoutTimer->Cancel();
346 mTimeoutTimer = nullptr;
350 void nsGeolocationRequest::SendLocation(nsIDOMGeoPosition* aPosition) {
351 if (mShutdown) {
352 // Ignore SendLocationEvents issued before we were cleared.
353 return;
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;
362 if (isTooOld) {
363 return;
367 RefPtr<mozilla::dom::GeolocationPosition> wrapped;
369 if (aPosition) {
370 nsCOMPtr<nsIDOMGeoPositionCoords> coords;
371 aPosition->GetCoords(getter_AddRefs(coords));
372 if (coords) {
373 wrapped = new mozilla::dom::GeolocationPosition(ToSupports(mLocator),
374 aPosition);
378 if (!wrapped) {
379 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE);
380 return;
383 if (!mIsWatchPositionRequest) {
384 // Cancel timer and position updates in case the position
385 // callback spins the event loop
386 Shutdown();
389 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
390 obs->NotifyObservers(wrapped, "geolocation-position-events",
391 u"location-updated");
393 nsAutoMicroTask mt;
394 if (mCallback.HasWebIDLCallback()) {
395 RefPtr<PositionCallback> callback = mCallback.GetWebIDLCallback();
397 MOZ_ASSERT(callback);
398 callback->Call(*wrapped);
399 } else {
400 nsIDOMGeoPositionCallback* callback = mCallback.GetXPCOMCallback();
401 MOZ_ASSERT(callback);
402 callback->HandleEvent(aPosition);
405 if (mIsWatchPositionRequest && !mShutdown) {
406 SetTimeoutTimer();
408 MOZ_ASSERT(mShutdown || mIsWatchPositionRequest,
409 "non-shutdown getCurrentPosition request after callback!");
412 nsIPrincipal* nsGeolocationRequest::GetPrincipal() {
413 if (!mLocator) {
414 return nullptr;
416 return mLocator->GetPrincipal();
419 NS_IMETHODIMP
420 nsGeolocationRequest::Update(nsIDOMGeoPosition* aPosition) {
421 nsCOMPtr<nsIRunnable> ev = new RequestSendLocationEvent(aPosition, this);
422 mMainThreadTarget->Dispatch(ev.forget());
423 return NS_OK;
426 NS_IMETHODIMP
427 nsGeolocationRequest::NotifyError(uint16_t aErrorCode) {
428 MOZ_ASSERT(NS_IsMainThread());
429 RefPtr<GeolocationPositionError> positionError =
430 new GeolocationPositionError(mLocator, aErrorCode);
431 positionError->NotifyCallback(mErrorCallback);
432 return NS_OK;
435 void nsGeolocationRequest::Shutdown() {
436 MOZ_ASSERT(!mShutdown, "request shutdown twice");
437 mShutdown = true;
439 StopTimeoutTimer();
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();
446 if (gs) {
447 gs->UpdateAccuracy();
452 ////////////////////////////////////////////////////
453 // nsGeolocationRequest::TimerCallbackHolder
454 ////////////////////////////////////////////////////
456 NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder, nsITimerCallback,
457 nsINamed)
459 NS_IMETHODIMP
460 nsGeolocationRequest::TimerCallbackHolder::Notify(nsITimer*) {
461 if (mRequest && mRequest->mLocator) {
462 RefPtr<nsGeolocationRequest> request(mRequest);
463 request->Notify();
466 return NS_OK;
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)
476 NS_INTERFACE_MAP_END
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()) {
487 return NS_OK;
490 // geolocation service can be enabled -> now register observer
491 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
492 if (!obs) {
493 return NS_ERROR_FAILURE;
496 obs->AddObserver(this, "xpcom-shutdown", false);
498 #ifdef MOZ_WIDGET_ANDROID
499 mProvider = new AndroidLocationProvider();
500 #endif
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);
516 # ifdef MOZ_GPSD
517 if (!mProvider && Preferences::GetBool("geo.provider.use_gpsd", false)) {
518 mProvider = new GpsdLocationProvider();
520 # endif
521 # endif
522 #endif
524 #ifdef MOZ_WIDGET_COCOA
525 if (Preferences::GetBool("geo.provider.use_corelocation", true)) {
526 mProvider = new CoreLocationLocationProvider();
528 #endif
530 #ifdef XP_WIN
531 if (Preferences::GetBool("geo.provider.ms-windows-location", false) &&
532 IsWin8OrLater()) {
533 mProvider = new WindowsLocationProvider();
535 #endif
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;
555 return NS_OK;
558 nsGeolocationService::~nsGeolocationService() = default;
560 NS_IMETHODIMP
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();
565 if (obs) {
566 obs->RemoveObserver(this, "xpcom-shutdown");
569 for (uint32_t i = 0; i < mGeolocators.Length(); i++) {
570 mGeolocators[i]->Shutdown();
572 StopDevice();
574 return NS_OK;
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();
582 return NS_OK;
585 // okay to close up.
586 StopDevice();
587 Update(nullptr);
588 return NS_OK;
591 return NS_ERROR_FAILURE;
594 NS_IMETHODIMP
595 nsGeolocationService::Update(nsIDOMGeoPosition* aSomewhere) {
596 if (aSomewhere) {
597 SetCachedPosition(aSomewhere);
600 for (uint32_t i = 0; i < mGeolocators.Length(); i++) {
601 mGeolocators[i]->Update(aSomewhere);
604 return NS_OK;
607 NS_IMETHODIMP
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);
616 return NS_OK;
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
634 // indefinitely.
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());
641 return NS_OK;
644 // Start them up!
645 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
646 if (!obs) {
647 return NS_ERROR_FAILURE;
650 if (!mProvider) {
651 return NS_ERROR_FAILURE;
654 nsresult rv;
656 if (NS_FAILED(rv = mProvider->Startup()) ||
657 NS_FAILED(rv = mProvider->Watch(this))) {
658 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE);
659 return rv;
662 obs->NotifyObservers(mProvider, "geolocation-device-events", u"starting");
664 return NS_OK;
667 void nsGeolocationService::SetDisconnectTimer() {
668 if (!mDisconnectTimer) {
669 mDisconnectTimer = NS_NewTimer();
670 } else {
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()) {
681 return true;
685 return false;
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);
697 return;
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();
718 if (!obs) {
719 return;
722 if (!mProvider) {
723 return;
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())) {
745 return nullptr;
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 ////////////////////////////////////////////////////
762 // Geolocation
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)
769 NS_INTERFACE_MAP_END
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() {
781 if (mService) {
782 Shutdown();
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
804 if (aContentDom) {
805 mOwner = do_GetWeakReference(aContentDom);
806 if (!mOwner) {
807 return NS_ERROR_FAILURE;
810 // Grab the principal of the document
811 nsCOMPtr<Document> doc = aContentDom->GetDoc();
812 if (!doc) {
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
827 // to prompt.
828 mService = nsGeolocationService::GetGeolocationService();
829 if (mService) {
830 mService->AddLocator(this);
833 return NS_OK;
836 void Geolocation::Shutdown() {
837 // Release all callbacks
838 mPendingCallbacks.Clear();
839 mWatchingCallbacks.Clear();
841 if (mService) {
842 mService->RemoveLocator(this);
843 mService->UpdateAccuracy();
846 mService = nullptr;
847 mPrincipal = nullptr;
850 nsPIDOMWindowInner* Geolocation::GetParentObject() const {
851 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mOwner);
852 return window.get();
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()) {
862 return true;
866 for (uint32_t i = 0; i < mPendingCallbacks.Length(); i++) {
867 if (mPendingCallbacks[i]->WantsHighAccuracy()) {
868 return true;
872 return false;
875 void Geolocation::RemoveRequest(nsGeolocationRequest* aRequest) {
876 bool requestWasKnown = (mPendingCallbacks.RemoveElement(aRequest) !=
877 mWatchingCallbacks.RemoveElement(aRequest));
879 Unused << requestWasKnown;
882 NS_IMETHODIMP
883 Geolocation::Update(nsIDOMGeoPosition* aSomewhere) {
884 if (!WindowOwnerStillExists()) {
885 Shutdown();
886 return NS_OK;
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
891 // update.
892 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(this->GetOwner());
893 if (window) {
894 nsCOMPtr<Document> document = window->GetDoc();
895 bool isHidden = document && document->Hidden();
896 if (isHidden || !window->IsFullyActive()) {
897 return NS_OK;
901 if (aSomewhere) {
902 nsCOMPtr<nsIDOMGeoPositionCoords> coords;
903 aSomewhere->GetCoords(getter_AddRefs(coords));
904 if (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);
922 return NS_OK;
925 NS_IMETHODIMP
926 Geolocation::NotifyError(uint16_t aErrorCode) {
927 if (!WindowOwnerStillExists()) {
928 Shutdown();
929 return NS_OK;
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);
946 return NS_OK;
949 bool Geolocation::IsFullyActiveOrChrome() {
950 // For regular content window, only allow this proceed if the window is "fully
951 // active".
952 if (nsPIDOMWindowInner* window = this->GetParentObject()) {
953 return window->IsFullyActive();
955 // Calls coming from chrome code don't have window, so we can proceed.
956 return true;
959 bool Geolocation::IsAlreadyCleared(nsGeolocationRequest* aRequest) {
960 for (uint32_t i = 0, length = mClearedWatchIDs.Length(); i < length; ++i) {
961 if (mClearedWatchIDs[i] == aRequest->WatchId()) {
962 return true;
966 return false;
969 bool Geolocation::ShouldBlockInsecureRequests() const {
970 if (Preferences::GetBool(PREF_GEO_SECURITY_ALLOWINSECURE, false)) {
971 return false;
974 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryReferent(mOwner);
975 if (!win) {
976 return false;
979 nsCOMPtr<Document> doc = win->GetDoc();
980 if (!doc) {
981 return false;
984 if (!nsGlobalWindowInner::Cast(win)->IsSecureContext()) {
985 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns, doc,
986 nsContentUtils::eDOM_PROPERTIES,
987 "GeolocationInsecureRequestIsForbidden");
988 return true;
991 return false;
994 bool Geolocation::ClearPendingRequest(nsGeolocationRequest* aRequest) {
995 if (aRequest->IsWatch() && this->IsAlreadyCleared(aRequest)) {
996 this->NotifyAllowedRequest(aRequest);
997 this->ClearWatch(aRequest->WatchId());
998 return true;
1001 return false;
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)) {
1013 aRv.Throw(rv);
1017 static nsIEventTarget* MainThreadTarget(Geolocation* geo) {
1018 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(geo->GetOwner());
1019 if (!window) {
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);
1035 return NS_OK;
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),
1047 target);
1049 if (!StaticPrefs::geo_enabled() || ShouldBlockInsecureRequests() ||
1050 !request->CheckPermissionDelegate()) {
1051 request->RequestDelayedTask(target,
1052 nsGeolocationRequest::DelayedTaskType::Deny);
1053 return NS_OK;
1056 if (!mOwner && aCallerType != CallerType::System) {
1057 return NS_ERROR_FAILURE;
1060 if (mOwner) {
1061 if (!RegisterRequestWithPrompt(request)) {
1062 return NS_ERROR_NOT_AVAILABLE;
1065 return NS_OK;
1068 if (aCallerType != CallerType::System) {
1069 return NS_ERROR_FAILURE;
1072 request->RequestDelayedTask(target,
1073 nsGeolocationRequest::DelayedTaskType::Allow);
1075 return NS_OK;
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);
1109 return 0;
1112 if (mWatchingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) {
1113 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
1114 return 0;
1117 // The watch ID:
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);
1129 return watchId;
1132 if (!mOwner && aCallerType != CallerType::System) {
1133 aRv.Throw(NS_ERROR_FAILURE);
1134 return 0;
1137 if (mOwner) {
1138 if (!RegisterRequestWithPrompt(request)) {
1139 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
1140 return 0;
1143 return watchId;
1146 if (aCallerType != CallerType::System) {
1147 aRv.Throw(NS_ERROR_FAILURE);
1148 return 0;
1151 request->Allow(JS::UndefinedHandleValue);
1152 return watchId;
1155 void Geolocation::ClearWatch(int32_t aWatchId) {
1156 if (aWatchId < 1) {
1157 return;
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);
1169 break;
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);
1180 break;
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) {
1190 return true;
1193 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mOwner);
1195 if (window) {
1196 nsPIDOMWindowOuter* outer = window->GetOuterWindow();
1197 if (!outer || outer->GetCurrentInnerWindow() != window || outer->Closed()) {
1198 return false;
1202 return true;
1205 void Geolocation::NotifyAllowedRequest(nsGeolocationRequest* aRequest) {
1206 if (aRequest->IsWatch()) {
1207 mWatchingCallbacks.AppendElement(aRequest);
1208 } else {
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);
1219 return true;
1221 if (pr == ContentPermissionRequestBase::PromptResult::Denied) {
1222 request->RequestDelayedTask(target,
1223 nsGeolocationRequest::DelayedTaskType::Deny);
1224 return true;
1227 request->RequestDelayedTask(target,
1228 nsGeolocationRequest::DelayedTaskType::Request);
1229 return true;
1232 JSObject* Geolocation::WrapObject(JSContext* aCtx,
1233 JS::Handle<JSObject*> aGivenProto) {
1234 return Geolocation_Binding::Wrap(aCtx, this, aGivenProto);