Bug 1892041 - Part 1: Update test262 features. r=spidermonkey-reviewers,dminor
[gecko.git] / dom / geolocation / Geolocation.cpp
blobcb9107deb1acfc6f9f3efe87144fcd9bbccd9231
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"
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 #endif
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
65 // default policy.
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 {
75 public:
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;
93 void Shutdown();
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();
105 MOZ_CAN_RUN_SCRIPT
106 void NotifyErrorAndShutdown(uint16_t);
107 using ContentPermissionRequestBase::GetPrincipal;
108 nsIPrincipal* GetPrincipal();
110 bool IsWatch() { return mIsWatchPositionRequest; }
111 int32_t WatchId() { return mWatchId; }
113 private:
114 virtual ~nsGeolocationRequest();
116 class TimerCallbackHolder final : public nsITimerCallback, public nsINamed {
117 public:
118 NS_DECL_ISUPPORTS
119 NS_DECL_NSITIMERCALLBACK
121 explicit TimerCallbackHolder(nsGeolocationRequest* aRequest)
122 : mRequest(aRequest) {}
124 NS_IMETHOD GetName(nsACString& aName) override {
125 aName.AssignLiteral("nsGeolocationRequest::TimerCallbackHolder");
126 return NS_OK;
129 private:
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;
146 int32_t mWatchId;
147 bool mShutdown;
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;
159 return geoOptions;
162 class RequestSendLocationEvent : public Runnable {
163 public:
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);
172 return NS_OK;
175 private:
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();
192 return raw;
195 nsGeolocationRequest::nsGeolocationRequest(
196 Geolocation* aLocator, GeoPositionCallback aCallback,
197 GeoPositionErrorCallback aErrorCallback,
198 UniquePtr<PositionOptions>&& aOptions,
199 nsIEventTarget* aMainThreadSerialEventTarget, bool aWatchPositionRequest,
200 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 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() {
227 SetTimeoutTimer();
228 NotifyErrorAndShutdown(GeolocationPositionError_Binding::TIMEOUT);
231 void nsGeolocationRequest::NotifyErrorAndShutdown(uint16_t aErrorCode) {
232 MOZ_ASSERT(!mShutdown, "timeout after shutdown");
233 if (!mIsWatchPositionRequest) {
234 Shutdown();
235 mLocator->RemoveRequest(this);
238 NotifyError(aErrorCode);
241 NS_IMETHODIMP
242 nsGeolocationRequest::Cancel() {
243 if (mLocator->ClearPendingRequest(this)) {
244 return NS_OK;
247 NotifyError(GeolocationPositionError_Binding::PERMISSION_DENIED);
248 return NS_OK;
251 NS_IMETHODIMP
252 nsGeolocationRequest::Allow(JS::Handle<JS::Value> aChoices) {
253 MOZ_ASSERT(aChoices.isUndefined());
255 if (mLocator->ClearPendingRequest(this)) {
256 return NS_OK;
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;
276 canUseCache =
277 isCachedWithinRequestedAccuracy && isCachedWithinRequestedTime;
281 gs->UpdateAccuracy(WantsHighAccuracy());
282 if (canUseCache) {
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) {
290 return NS_OK;
293 } else {
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);
298 return NS_OK;
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();
313 if (NS_FAILED(rv)) {
314 if (allowedRequest) {
315 mLocator->RemoveRequest(this);
317 // Location provider error
318 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE);
319 return NS_OK;
322 SetTimeoutTimer();
324 return NS_OK;
327 void nsGeolocationRequest::SetTimeoutTimer() {
328 MOZ_ASSERT(!mShutdown, "set timeout after shutdown");
330 StopTimeoutTimer();
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() {
340 if (mTimeoutTimer) {
341 mTimeoutTimer->Cancel();
342 mTimeoutTimer = nullptr;
346 void nsGeolocationRequest::SendLocation(nsIDOMGeoPosition* aPosition) {
347 if (mShutdown) {
348 // Ignore SendLocationEvents issued before we were cleared.
349 return;
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;
358 if (isTooOld) {
359 return;
363 RefPtr<mozilla::dom::GeolocationPosition> wrapped;
365 if (aPosition) {
366 nsCOMPtr<nsIDOMGeoPositionCoords> coords;
367 aPosition->GetCoords(getter_AddRefs(coords));
368 if (coords) {
369 wrapped = new mozilla::dom::GeolocationPosition(ToSupports(mLocator),
370 aPosition);
374 if (!wrapped) {
375 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE);
376 return;
379 if (!mIsWatchPositionRequest) {
380 // Cancel timer and position updates in case the position
381 // callback spins the event loop
382 Shutdown();
385 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
386 obs->NotifyObservers(wrapped, "geolocation-position-events",
387 u"location-updated");
389 nsAutoMicroTask mt;
390 if (mCallback.HasWebIDLCallback()) {
391 RefPtr<PositionCallback> callback = mCallback.GetWebIDLCallback();
393 MOZ_ASSERT(callback);
394 callback->Call(*wrapped);
395 } else {
396 nsIDOMGeoPositionCallback* callback = mCallback.GetXPCOMCallback();
397 MOZ_ASSERT(callback);
398 callback->HandleEvent(aPosition);
401 if (mIsWatchPositionRequest && !mShutdown) {
402 SetTimeoutTimer();
404 MOZ_ASSERT(mShutdown || mIsWatchPositionRequest,
405 "non-shutdown getCurrentPosition request after callback!");
408 nsIPrincipal* nsGeolocationRequest::GetPrincipal() {
409 if (!mLocator) {
410 return nullptr;
412 return mLocator->GetPrincipal();
415 NS_IMETHODIMP
416 nsGeolocationRequest::Update(nsIDOMGeoPosition* aPosition) {
417 nsCOMPtr<nsIRunnable> ev = new RequestSendLocationEvent(aPosition, this);
418 mMainThreadSerialEventTarget->Dispatch(ev.forget());
419 return NS_OK;
422 NS_IMETHODIMP
423 nsGeolocationRequest::NotifyError(uint16_t aErrorCode) {
424 MOZ_ASSERT(NS_IsMainThread());
425 RefPtr<GeolocationPositionError> positionError =
426 new GeolocationPositionError(mLocator, aErrorCode);
427 positionError->NotifyCallback(mErrorCallback);
428 return NS_OK;
431 void nsGeolocationRequest::Shutdown() {
432 MOZ_ASSERT(!mShutdown, "request shutdown twice");
433 mShutdown = true;
435 StopTimeoutTimer();
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();
442 if (gs) {
443 gs->UpdateAccuracy();
448 ////////////////////////////////////////////////////
449 // nsGeolocationRequest::TimerCallbackHolder
450 ////////////////////////////////////////////////////
452 NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder, nsITimerCallback,
453 nsINamed)
455 NS_IMETHODIMP
456 nsGeolocationRequest::TimerCallbackHolder::Notify(nsITimer*) {
457 if (mRequest && mRequest->mLocator) {
458 RefPtr<nsGeolocationRequest> request(mRequest);
459 request->Notify();
462 return NS_OK;
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)
472 NS_INTERFACE_MAP_END
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()) {
483 return NS_OK;
486 // geolocation service can be enabled -> now register observer
487 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
488 if (!obs) {
489 return NS_ERROR_FAILURE;
492 obs->AddObserver(this, "xpcom-shutdown", false);
494 #ifdef MOZ_WIDGET_ANDROID
495 mProvider = new AndroidLocationProvider();
496 #endif
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);
512 # ifdef MOZ_GPSD
513 if (!mProvider && Preferences::GetBool("geo.provider.use_gpsd", false)) {
514 mProvider = new GpsdLocationProvider();
516 # endif
517 # endif
518 #endif
520 #ifdef MOZ_WIDGET_COCOA
521 if (Preferences::GetBool("geo.provider.use_corelocation", true)) {
522 mProvider = new CoreLocationLocationProvider();
524 #endif
526 #ifdef XP_WIN
527 if (Preferences::GetBool("geo.provider.ms-windows-location", false)) {
528 mProvider = new WindowsLocationProvider();
530 #endif
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;
550 return NS_OK;
553 nsGeolocationService::~nsGeolocationService() = default;
555 NS_IMETHODIMP
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();
560 if (obs) {
561 obs->RemoveObserver(this, "xpcom-shutdown");
564 for (uint32_t i = 0; i < mGeolocators.Length(); i++) {
565 mGeolocators[i]->Shutdown();
567 StopDevice();
569 return NS_OK;
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();
577 return NS_OK;
580 // okay to close up.
581 StopDevice();
582 Update(nullptr);
583 return NS_OK;
586 return NS_ERROR_FAILURE;
589 NS_IMETHODIMP
590 nsGeolocationService::Update(nsIDOMGeoPosition* aSomewhere) {
591 if (aSomewhere) {
592 SetCachedPosition(aSomewhere);
595 for (uint32_t i = 0; i < mGeolocators.Length(); i++) {
596 mGeolocators[i]->Update(aSomewhere);
599 return NS_OK;
602 NS_IMETHODIMP
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);
611 return NS_OK;
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
629 // indefinitely.
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());
636 return NS_OK;
639 // Start them up!
640 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
641 if (!obs) {
642 return NS_ERROR_FAILURE;
645 if (!mProvider) {
646 return NS_ERROR_FAILURE;
649 nsresult rv;
651 if (NS_FAILED(rv = mProvider->Startup()) ||
652 NS_FAILED(rv = mProvider->Watch(this))) {
653 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE);
654 return rv;
657 obs->NotifyObservers(mProvider, "geolocation-device-events", u"starting");
659 return NS_OK;
662 void nsGeolocationService::SetDisconnectTimer() {
663 if (!mDisconnectTimer) {
664 mDisconnectTimer = NS_NewTimer();
665 } else {
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()) {
676 return true;
680 return false;
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);
692 return;
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();
713 if (!obs) {
714 return;
717 if (!mProvider) {
718 return;
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())) {
740 return nullptr;
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 ////////////////////////////////////////////////////
757 // Geolocation
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)
764 NS_INTERFACE_MAP_END
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() {
776 if (mService) {
777 Shutdown();
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
799 if (aContentDom) {
800 mOwner = do_GetWeakReference(aContentDom);
801 if (!mOwner) {
802 return NS_ERROR_FAILURE;
805 // Grab the principal of the document
806 nsCOMPtr<Document> doc = aContentDom->GetDoc();
807 if (!doc) {
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
822 // to prompt.
823 mService = nsGeolocationService::GetGeolocationService();
824 if (mService) {
825 mService->AddLocator(this);
828 return NS_OK;
831 void Geolocation::Shutdown() {
832 // Release all callbacks
833 mPendingCallbacks.Clear();
834 mWatchingCallbacks.Clear();
836 if (mService) {
837 mService->RemoveLocator(this);
838 mService->UpdateAccuracy();
841 mService = nullptr;
842 mPrincipal = nullptr;
845 nsPIDOMWindowInner* Geolocation::GetParentObject() const {
846 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mOwner);
847 return window.get();
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()) {
857 return true;
861 for (uint32_t i = 0; i < mPendingCallbacks.Length(); i++) {
862 if (mPendingCallbacks[i]->WantsHighAccuracy()) {
863 return true;
867 return false;
870 void Geolocation::RemoveRequest(nsGeolocationRequest* aRequest) {
871 bool requestWasKnown = (mPendingCallbacks.RemoveElement(aRequest) !=
872 mWatchingCallbacks.RemoveElement(aRequest));
874 Unused << requestWasKnown;
877 NS_IMETHODIMP
878 Geolocation::Update(nsIDOMGeoPosition* aSomewhere) {
879 if (!WindowOwnerStillExists()) {
880 Shutdown();
881 return NS_OK;
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
886 // update.
887 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(this->GetOwner());
888 if (window) {
889 nsCOMPtr<Document> document = window->GetDoc();
890 bool isHidden = document && document->Hidden();
891 if (isHidden || !window->IsFullyActive()) {
892 return NS_OK;
896 if (aSomewhere) {
897 nsCOMPtr<nsIDOMGeoPositionCoords> coords;
898 aSomewhere->GetCoords(getter_AddRefs(coords));
899 if (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);
917 return NS_OK;
920 NS_IMETHODIMP
921 Geolocation::NotifyError(uint16_t aErrorCode) {
922 if (!WindowOwnerStillExists()) {
923 Shutdown();
924 return NS_OK;
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);
941 return NS_OK;
944 bool Geolocation::IsFullyActiveOrChrome() {
945 // For regular content window, only allow this proceed if the window is "fully
946 // active".
947 if (nsPIDOMWindowInner* window = this->GetParentObject()) {
948 return window->IsFullyActive();
950 // Calls coming from chrome code don't have window, so we can proceed.
951 return true;
954 bool Geolocation::IsAlreadyCleared(nsGeolocationRequest* aRequest) {
955 for (uint32_t i = 0, length = mClearedWatchIDs.Length(); i < length; ++i) {
956 if (mClearedWatchIDs[i] == aRequest->WatchId()) {
957 return true;
961 return false;
964 bool Geolocation::ShouldBlockInsecureRequests() const {
965 if (Preferences::GetBool(PREF_GEO_SECURITY_ALLOWINSECURE, false)) {
966 return false;
969 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryReferent(mOwner);
970 if (!win) {
971 return false;
974 nsCOMPtr<Document> doc = win->GetDoc();
975 if (!doc) {
976 return false;
979 if (!nsGlobalWindowInner::Cast(win)->IsSecureContext()) {
980 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns, doc,
981 nsContentUtils::eDOM_PROPERTIES,
982 "GeolocationInsecureRequestIsForbidden");
983 return true;
986 return false;
989 bool Geolocation::ClearPendingRequest(nsGeolocationRequest* aRequest) {
990 if (aRequest->IsWatch() && this->IsAlreadyCleared(aRequest)) {
991 this->NotifyAllowedRequest(aRequest);
992 this->ClearWatch(aRequest->WatchId());
993 return true;
996 return false;
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)) {
1008 aRv.Throw(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);
1021 return NS_OK;
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),
1033 target);
1035 if (!StaticPrefs::geo_enabled() || ShouldBlockInsecureRequests() ||
1036 !request->CheckPermissionDelegate()) {
1037 request->RequestDelayedTask(target,
1038 nsGeolocationRequest::DelayedTaskType::Deny);
1039 return NS_OK;
1042 if (!mOwner && aCallerType != CallerType::System) {
1043 return NS_ERROR_FAILURE;
1046 if (mOwner) {
1047 if (!RegisterRequestWithPrompt(request)) {
1048 return NS_ERROR_NOT_AVAILABLE;
1051 return NS_OK;
1054 if (aCallerType != CallerType::System) {
1055 return NS_ERROR_FAILURE;
1058 request->RequestDelayedTask(target,
1059 nsGeolocationRequest::DelayedTaskType::Allow);
1061 return NS_OK;
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);
1095 return 0;
1098 if (mWatchingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) {
1099 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
1100 return 0;
1103 // The watch ID:
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);
1115 return watchId;
1118 if (!mOwner && aCallerType != CallerType::System) {
1119 aRv.Throw(NS_ERROR_FAILURE);
1120 return 0;
1123 if (mOwner) {
1124 if (!RegisterRequestWithPrompt(request)) {
1125 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
1126 return 0;
1129 return watchId;
1132 if (aCallerType != CallerType::System) {
1133 aRv.Throw(NS_ERROR_FAILURE);
1134 return 0;
1137 request->Allow(JS::UndefinedHandleValue);
1138 return watchId;
1141 void Geolocation::ClearWatch(int32_t aWatchId) {
1142 if (aWatchId < 1) {
1143 return;
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);
1155 break;
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);
1166 break;
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) {
1176 return true;
1179 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mOwner);
1181 if (window) {
1182 nsPIDOMWindowOuter* outer = window->GetOuterWindow();
1183 if (!outer || outer->GetCurrentInnerWindow() != window || outer->Closed()) {
1184 return false;
1188 return true;
1191 void Geolocation::NotifyAllowedRequest(nsGeolocationRequest* aRequest) {
1192 if (aRequest->IsWatch()) {
1193 mWatchingCallbacks.AppendElement(aRequest);
1194 } else {
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);
1205 return true;
1207 if (pr == ContentPermissionRequestBase::PromptResult::Denied) {
1208 request->RequestDelayedTask(target,
1209 nsGeolocationRequest::DelayedTaskType::Deny);
1210 return true;
1213 request->RequestDelayedTask(target,
1214 nsGeolocationRequest::DelayedTaskType::Request);
1215 return true;
1218 JSObject* Geolocation::WrapObject(JSContext* aCtx,
1219 JS::Handle<JSObject*> aGivenProto) {
1220 return Geolocation_Binding::Wrap(aCtx, this, aGivenProto);