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