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/. */
6
7 #include "Geolocation.h"
8
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"
35
36 class nsIPrincipal;
37
38 #ifdef MOZ_WIDGET_ANDROID
39 # include "AndroidLocationProvider.h"
40 #endif
41
42 #ifdef MOZ_GPSD
43 # include "GpsdLocationProvider.h"
44 #endif
45
46 #ifdef MOZ_WIDGET_COCOA
47 # include "CoreLocationLocationProvider.h"
48 #endif
49
50 #ifdef XP_WIN
51 # include "WindowsLocationProvider.h"
52 # include "mozilla/WindowsVersion.h"
53 #endif
54
55 // Some limit to the number of get or watch geolocation requests
56 // that a window can make.
57 #define MAX_GEO_REQUESTS_PER_WINDOW 1500
58
59 // This preference allows to override the "secure context" by
60 // default policy.
61 #define PREF_GEO_SECURITY_ALLOWINSECURE "geo.security.allowinsecure"
62
63 using mozilla::Unused; // <snicker>
64 using namespace mozilla;
65 using namespace mozilla::dom;
66
67 class nsGeolocationRequest final : public ContentPermissionRequestBase,
68 public nsIGeolocationUpdate,
69 public SupportsWeakPtr {
70 public:
71 NS_DECL_ISUPPORTS_INHERITED
72 NS_DECL_NSIGEOLOCATIONUPDATE
73
74 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsGeolocationRequest,
75 ContentPermissionRequestBase)
76
77 nsGeolocationRequest(Geolocation* aLocator, GeoPositionCallback aCallback,
78 GeoPositionErrorCallback aErrorCallback,
79 UniquePtr<PositionOptions>&& aOptions,
80 nsIEventTarget* aMainThreadTarget,
81 bool aWatchPositionRequest = false,
82 int32_t aWatchId = 0);
83
84 // nsIContentPermissionRequest
85 MOZ_CAN_RUN_SCRIPT NS_IMETHOD Cancel(void) override;
86 MOZ_CAN_RUN_SCRIPT NS_IMETHOD Allow(JS::HandleValue choices) override;
87
88 void Shutdown();
89
90 // MOZ_CAN_RUN_SCRIPT_BOUNDARY is OK here because we're always called from a
91 // runnable. Ideally nsIRunnable::Run and its overloads would just be
92 // MOZ_CAN_RUN_SCRIPT and then we could be too...
93 MOZ_CAN_RUN_SCRIPT_BOUNDARY
94 void SendLocation(nsIDOMGeoPosition* aLocation);
WantsHighAccuracy()95 bool WantsHighAccuracy() {
96 return !mShutdown && mOptions && mOptions->mEnableHighAccuracy;
97 }
98 void SetTimeoutTimer();
99 void StopTimeoutTimer();
100 MOZ_CAN_RUN_SCRIPT
101 void NotifyErrorAndShutdown(uint16_t);
102 using ContentPermissionRequestBase::GetPrincipal;
103 nsIPrincipal* GetPrincipal();
104
IsWatch()105 bool IsWatch() { return mIsWatchPositionRequest; }
WatchId()106 int32_t WatchId() { return mWatchId; }
107
108 private:
109 virtual ~nsGeolocationRequest();
110
111 class TimerCallbackHolder final : public nsITimerCallback, public nsINamed {
112 public:
113 NS_DECL_ISUPPORTS
114 NS_DECL_NSITIMERCALLBACK
115
TimerCallbackHolder(nsGeolocationRequest * aRequest)116 explicit TimerCallbackHolder(nsGeolocationRequest* aRequest)
117 : mRequest(aRequest) {}
118
GetName(nsACString & aName)119 NS_IMETHOD GetName(nsACString& aName) override {
120 aName.AssignLiteral("nsGeolocationRequest::TimerCallbackHolder");
121 return NS_OK;
122 }
123
124 private:
125 ~TimerCallbackHolder() = default;
126 WeakPtr<nsGeolocationRequest> mRequest;
127 };
128
129 // Only called from a timer, so MOZ_CAN_RUN_SCRIPT_BOUNDARY ok for now.
130 MOZ_CAN_RUN_SCRIPT_BOUNDARY void Notify();
131
132 bool mIsWatchPositionRequest;
133
134 nsCOMPtr<nsITimer> mTimeoutTimer;
135 GeoPositionCallback mCallback;
136 GeoPositionErrorCallback mErrorCallback;
137 UniquePtr<PositionOptions> mOptions;
138
139 RefPtr<Geolocation> mLocator;
140
141 int32_t mWatchId;
142 bool mShutdown;
143 nsCOMPtr<nsIEventTarget> mMainThreadTarget;
144 };
145
CreatePositionOptionsCopy(const PositionOptions & aOptions)146 static UniquePtr<PositionOptions> CreatePositionOptionsCopy(
147 const PositionOptions& aOptions) {
148 UniquePtr<PositionOptions> geoOptions = MakeUnique<PositionOptions>();
149
150 geoOptions->mEnableHighAccuracy = aOptions.mEnableHighAccuracy;
151 geoOptions->mMaximumAge = aOptions.mMaximumAge;
152 geoOptions->mTimeout = aOptions.mTimeout;
153
154 return geoOptions;
155 }
156
157 class RequestSendLocationEvent : public Runnable {
158 public:
RequestSendLocationEvent(nsIDOMGeoPosition * aPosition,nsGeolocationRequest * aRequest)159 RequestSendLocationEvent(nsIDOMGeoPosition* aPosition,
160 nsGeolocationRequest* aRequest)
161 : mozilla::Runnable("RequestSendLocationEvent"),
162 mPosition(aPosition),
163 mRequest(aRequest) {}
164
Run()165 NS_IMETHOD Run() override {
166 mRequest->SendLocation(mPosition);
167 return NS_OK;
168 }
169
170 private:
171 nsCOMPtr<nsIDOMGeoPosition> mPosition;
172 RefPtr<nsGeolocationRequest> mRequest;
173 RefPtr<Geolocation> mLocator;
174 };
175
176 ////////////////////////////////////////////////////
177 // nsGeolocationRequest
178 ////////////////////////////////////////////////////
179
ConvertWeakReferenceToWindow(nsIWeakReference * aWeakPtr)180 static nsPIDOMWindowInner* ConvertWeakReferenceToWindow(
181 nsIWeakReference* aWeakPtr) {
182 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(aWeakPtr);
183 // This isn't usually safe, but here we're just extracting a raw pointer in
184 // order to pass it to a base class constructor which will in turn convert it
185 // into a strong pointer for us.
186 nsPIDOMWindowInner* raw = window.get();
187 return raw;
188 }
189
nsGeolocationRequest(Geolocation * aLocator,GeoPositionCallback aCallback,GeoPositionErrorCallback aErrorCallback,UniquePtr<PositionOptions> && aOptions,nsIEventTarget * aMainThreadTarget,bool aWatchPositionRequest,int32_t aWatchId)190 nsGeolocationRequest::nsGeolocationRequest(
191 Geolocation* aLocator, GeoPositionCallback aCallback,
192 GeoPositionErrorCallback aErrorCallback,
193 UniquePtr<PositionOptions>&& aOptions, nsIEventTarget* aMainThreadTarget,
194 bool aWatchPositionRequest, int32_t aWatchId)
195 : ContentPermissionRequestBase(
196 aLocator->GetPrincipal(),
197 ConvertWeakReferenceToWindow(aLocator->GetOwner()), "geo"_ns,
198 "geolocation"_ns),
199 mIsWatchPositionRequest(aWatchPositionRequest),
200 mCallback(std::move(aCallback)),
201 mErrorCallback(std::move(aErrorCallback)),
202 mOptions(std::move(aOptions)),
203 mLocator(aLocator),
204 mWatchId(aWatchId),
205 mShutdown(false),
206 mMainThreadTarget(aMainThreadTarget) {
207 if (nsCOMPtr<nsPIDOMWindowInner> win =
208 do_QueryReferent(mLocator->GetOwner())) {
209 }
210 }
211
~nsGeolocationRequest()212 nsGeolocationRequest::~nsGeolocationRequest() { StopTimeoutTimer(); }
213
NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(nsGeolocationRequest,ContentPermissionRequestBase,nsIGeolocationUpdate)214 NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(nsGeolocationRequest,
215 ContentPermissionRequestBase,
216 nsIGeolocationUpdate)
217
218 NS_IMPL_ADDREF_INHERITED(nsGeolocationRequest, ContentPermissionRequestBase)
219 NS_IMPL_RELEASE_INHERITED(nsGeolocationRequest, ContentPermissionRequestBase)
220 NS_IMPL_CYCLE_COLLECTION_WEAK_PTR_INHERITED(nsGeolocationRequest,
221 ContentPermissionRequestBase,
222 mCallback, mErrorCallback, mLocator)
223
224 void nsGeolocationRequest::Notify() {
225 SetTimeoutTimer();
226 NotifyErrorAndShutdown(GeolocationPositionError_Binding::TIMEOUT);
227 }
228
NotifyErrorAndShutdown(uint16_t aErrorCode)229 void nsGeolocationRequest::NotifyErrorAndShutdown(uint16_t aErrorCode) {
230 MOZ_ASSERT(!mShutdown, "timeout after shutdown");
231 if (!mIsWatchPositionRequest) {
232 Shutdown();
233 mLocator->RemoveRequest(this);
234 }
235
236 NotifyError(aErrorCode);
237 }
238
239 NS_IMETHODIMP
Cancel()240 nsGeolocationRequest::Cancel() {
241 if (mLocator->ClearPendingRequest(this)) {
242 return NS_OK;
243 }
244
245 NotifyError(GeolocationPositionError_Binding::PERMISSION_DENIED);
246 return NS_OK;
247 }
248
249 NS_IMETHODIMP
Allow(JS::HandleValue aChoices)250 nsGeolocationRequest::Allow(JS::HandleValue aChoices) {
251 MOZ_ASSERT(aChoices.isUndefined());
252
253 if (mLocator->ClearPendingRequest(this)) {
254 return NS_OK;
255 }
256
257 RefPtr<nsGeolocationService> gs =
258 nsGeolocationService::GetGeolocationService();
259
260 bool canUseCache = false;
261 CachedPositionAndAccuracy lastPosition = gs->GetCachedPosition();
262 if (lastPosition.position) {
263 DOMTimeStamp cachedPositionTime_ms;
264 lastPosition.position->GetTimestamp(&cachedPositionTime_ms);
265 // check to see if we can use a cached value
266 // if the user has specified a maximumAge, return a cached value.
267 if (mOptions && mOptions->mMaximumAge > 0) {
268 uint32_t maximumAge_ms = mOptions->mMaximumAge;
269 bool isCachedWithinRequestedAccuracy =
270 WantsHighAccuracy() <= lastPosition.isHighAccuracy;
271 bool isCachedWithinRequestedTime =
272 DOMTimeStamp(PR_Now() / PR_USEC_PER_MSEC - maximumAge_ms) <=
273 cachedPositionTime_ms;
274 canUseCache =
275 isCachedWithinRequestedAccuracy && isCachedWithinRequestedTime;
276 }
277 }
278
279 gs->UpdateAccuracy(WantsHighAccuracy());
280 if (canUseCache) {
281 // okay, we can return a cached position
282 // getCurrentPosition requests serviced by the cache
283 // will now be owned by the RequestSendLocationEvent
284 Update(lastPosition.position);
285
286 // After Update is called, getCurrentPosition finishes it's job.
287 if (!mIsWatchPositionRequest) {
288 return NS_OK;
289 }
290
291 } else {
292 // if it is not a watch request and timeout is 0,
293 // invoke the errorCallback (if present) with TIMEOUT code
294 if (mOptions && mOptions->mTimeout == 0 && !mIsWatchPositionRequest) {
295 NotifyError(GeolocationPositionError_Binding::TIMEOUT);
296 return NS_OK;
297 }
298 }
299
300 // Kick off the geo device, if it isn't already running
301 nsCOMPtr<nsIPrincipal> principal = GetPrincipal();
302 nsresult rv = gs->StartDevice(principal);
303
304 if (NS_FAILED(rv)) {
305 // Location provider error
306 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE);
307 return NS_OK;
308 }
309
310 if (mIsWatchPositionRequest || !canUseCache) {
311 // let the locator know we're pending
312 // we will now be owned by the locator
313 mLocator->NotifyAllowedRequest(this);
314 }
315
316 SetTimeoutTimer();
317
318 return NS_OK;
319 }
320
SetTimeoutTimer()321 void nsGeolocationRequest::SetTimeoutTimer() {
322 MOZ_ASSERT(!mShutdown, "set timeout after shutdown");
323
324 StopTimeoutTimer();
325
326 if (mOptions && mOptions->mTimeout != 0 && mOptions->mTimeout != 0x7fffffff) {
327 RefPtr<TimerCallbackHolder> holder = new TimerCallbackHolder(this);
328 NS_NewTimerWithCallback(getter_AddRefs(mTimeoutTimer), holder,
329 mOptions->mTimeout, nsITimer::TYPE_ONE_SHOT);
330 }
331 }
332
StopTimeoutTimer()333 void nsGeolocationRequest::StopTimeoutTimer() {
334 if (mTimeoutTimer) {
335 mTimeoutTimer->Cancel();
336 mTimeoutTimer = nullptr;
337 }
338 }
339
SendLocation(nsIDOMGeoPosition * aPosition)340 void nsGeolocationRequest::SendLocation(nsIDOMGeoPosition* aPosition) {
341 if (mShutdown) {
342 // Ignore SendLocationEvents issued before we were cleared.
343 return;
344 }
345
346 if (mOptions && mOptions->mMaximumAge > 0) {
347 DOMTimeStamp positionTime_ms;
348 aPosition->GetTimestamp(&positionTime_ms);
349 const uint32_t maximumAge_ms = mOptions->mMaximumAge;
350 const bool isTooOld = DOMTimeStamp(PR_Now() / PR_USEC_PER_MSEC -
351 maximumAge_ms) > positionTime_ms;
352 if (isTooOld) {
353 return;
354 }
355 }
356
357 RefPtr<mozilla::dom::GeolocationPosition> wrapped;
358
359 if (aPosition) {
360 nsCOMPtr<nsIDOMGeoPositionCoords> coords;
361 aPosition->GetCoords(getter_AddRefs(coords));
362 if (coords) {
363 wrapped = new mozilla::dom::GeolocationPosition(ToSupports(mLocator),
364 aPosition);
365 }
366 }
367
368 if (!wrapped) {
369 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE);
370 return;
371 }
372
373 if (!mIsWatchPositionRequest) {
374 // Cancel timer and position updates in case the position
375 // callback spins the event loop
376 Shutdown();
377 }
378
379 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
380 obs->NotifyObservers(wrapped, "geolocation-position-events",
381 u"location-updated");
382
383 nsAutoMicroTask mt;
384 if (mCallback.HasWebIDLCallback()) {
385 RefPtr<PositionCallback> callback = mCallback.GetWebIDLCallback();
386
387 MOZ_ASSERT(callback);
388 callback->Call(*wrapped);
389 } else {
390 nsIDOMGeoPositionCallback* callback = mCallback.GetXPCOMCallback();
391 MOZ_ASSERT(callback);
392 callback->HandleEvent(aPosition);
393 }
394
395 if (mIsWatchPositionRequest && !mShutdown) {
396 SetTimeoutTimer();
397 }
398 MOZ_ASSERT(mShutdown || mIsWatchPositionRequest,
399 "non-shutdown getCurrentPosition request after callback!");
400 }
401
GetPrincipal()402 nsIPrincipal* nsGeolocationRequest::GetPrincipal() {
403 if (!mLocator) {
404 return nullptr;
405 }
406 return mLocator->GetPrincipal();
407 }
408
409 NS_IMETHODIMP
Update(nsIDOMGeoPosition * aPosition)410 nsGeolocationRequest::Update(nsIDOMGeoPosition* aPosition) {
411 nsCOMPtr<nsIRunnable> ev = new RequestSendLocationEvent(aPosition, this);
412 mMainThreadTarget->Dispatch(ev.forget());
413 return NS_OK;
414 }
415
416 NS_IMETHODIMP
NotifyError(uint16_t aErrorCode)417 nsGeolocationRequest::NotifyError(uint16_t aErrorCode) {
418 MOZ_ASSERT(NS_IsMainThread());
419 RefPtr<GeolocationPositionError> positionError =
420 new GeolocationPositionError(mLocator, aErrorCode);
421 positionError->NotifyCallback(mErrorCallback);
422 return NS_OK;
423 }
424
Shutdown()425 void nsGeolocationRequest::Shutdown() {
426 MOZ_ASSERT(!mShutdown, "request shutdown twice");
427 mShutdown = true;
428
429 StopTimeoutTimer();
430
431 // If there are no other high accuracy requests, the geolocation service will
432 // notify the provider to switch to the default accuracy.
433 if (mOptions && mOptions->mEnableHighAccuracy) {
434 RefPtr<nsGeolocationService> gs =
435 nsGeolocationService::GetGeolocationService();
436 if (gs) {
437 gs->UpdateAccuracy();
438 }
439 }
440 }
441
442 ////////////////////////////////////////////////////
443 // nsGeolocationRequest::TimerCallbackHolder
444 ////////////////////////////////////////////////////
445
NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder,nsITimerCallback,nsINamed)446 NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder, nsITimerCallback,
447 nsINamed)
448
449 NS_IMETHODIMP
450 nsGeolocationRequest::TimerCallbackHolder::Notify(nsITimer*) {
451 if (mRequest && mRequest->mLocator) {
452 RefPtr<nsGeolocationRequest> request(mRequest);
453 request->Notify();
454 }
455
456 return NS_OK;
457 }
458
459 ////////////////////////////////////////////////////
460 // nsGeolocationService
461 ////////////////////////////////////////////////////
462 NS_INTERFACE_MAP_BEGIN(nsGeolocationService)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,nsIGeolocationUpdate)463 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIGeolocationUpdate)
464 NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate)
465 NS_INTERFACE_MAP_ENTRY(nsIObserver)
466 NS_INTERFACE_MAP_END
467
468 NS_IMPL_ADDREF(nsGeolocationService)
469 NS_IMPL_RELEASE(nsGeolocationService)
470
471 nsresult nsGeolocationService::Init() {
472 if (!StaticPrefs::geo_enabled()) {
473 return NS_ERROR_FAILURE;
474 }
475
476 if (XRE_IsContentProcess()) {
477 return NS_OK;
478 }
479
480 // geolocation service can be enabled -> now register observer
481 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
482 if (!obs) {
483 return NS_ERROR_FAILURE;
484 }
485
486 obs->AddObserver(this, "xpcom-shutdown", false);
487
488 #ifdef MOZ_WIDGET_ANDROID
489 mProvider = new AndroidLocationProvider();
490 #endif
491
492 #ifdef MOZ_WIDGET_GTK
493 # ifdef MOZ_GPSD
494 if (Preferences::GetBool("geo.provider.use_gpsd", false)) {
495 mProvider = new GpsdLocationProvider();
496 }
497 # endif
498 #endif
499
500 #ifdef MOZ_WIDGET_COCOA
501 if (Preferences::GetBool("geo.provider.use_corelocation", true)) {
502 mProvider = new CoreLocationLocationProvider();
503 }
504 #endif
505
506 #ifdef XP_WIN
507 if (Preferences::GetBool("geo.provider.ms-windows-location", false) &&
508 IsWin8OrLater()) {
509 mProvider = new WindowsLocationProvider();
510 }
511 #endif
512
513 if (Preferences::GetBool("geo.provider.use_mls", false)) {
514 mProvider = do_CreateInstance("@mozilla.org/geolocation/mls-provider;1");
515 }
516
517 // Override platform-specific providers with the default (network)
518 // provider while testing. Our tests are currently not meant to exercise
519 // the provider, and some tests rely on the network provider being used.
520 // "geo.provider.testing" is always set for all plain and browser chrome
521 // mochitests, and also for xpcshell tests.
522 if (!mProvider || Preferences::GetBool("geo.provider.testing", false)) {
523 nsCOMPtr<nsIGeolocationProvider> geoTestProvider =
524 do_GetService(NS_GEOLOCATION_PROVIDER_CONTRACTID);
525
526 if (geoTestProvider) {
527 mProvider = geoTestProvider;
528 }
529 }
530
531 return NS_OK;
532 }
533
534 nsGeolocationService::~nsGeolocationService() = default;
535
536 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)537 nsGeolocationService::Observe(nsISupports* aSubject, const char* aTopic,
538 const char16_t* aData) {
539 if (!strcmp("xpcom-shutdown", aTopic)) {
540 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
541 if (obs) {
542 obs->RemoveObserver(this, "xpcom-shutdown");
543 }
544
545 for (uint32_t i = 0; i < mGeolocators.Length(); i++) {
546 mGeolocators[i]->Shutdown();
547 }
548 StopDevice();
549
550 return NS_OK;
551 }
552
553 if (!strcmp("timer-callback", aTopic)) {
554 // decide if we can close down the service.
555 for (uint32_t i = 0; i < mGeolocators.Length(); i++)
556 if (mGeolocators[i]->HasActiveCallbacks()) {
557 SetDisconnectTimer();
558 return NS_OK;
559 }
560
561 // okay to close up.
562 StopDevice();
563 Update(nullptr);
564 return NS_OK;
565 }
566
567 return NS_ERROR_FAILURE;
568 }
569
570 NS_IMETHODIMP
Update(nsIDOMGeoPosition * aSomewhere)571 nsGeolocationService::Update(nsIDOMGeoPosition* aSomewhere) {
572 if (aSomewhere) {
573 SetCachedPosition(aSomewhere);
574 }
575
576 for (uint32_t i = 0; i < mGeolocators.Length(); i++) {
577 mGeolocators[i]->Update(aSomewhere);
578 }
579
580 return NS_OK;
581 }
582
583 NS_IMETHODIMP
NotifyError(uint16_t aErrorCode)584 nsGeolocationService::NotifyError(uint16_t aErrorCode) {
585 // nsTArray doesn't have a constructors that takes a different-type TArray.
586 nsTArray<RefPtr<Geolocation>> geolocators;
587 geolocators.AppendElements(mGeolocators);
588 for (uint32_t i = 0; i < geolocators.Length(); i++) {
589 // MOZ_KnownLive because the stack array above keeps it alive.
590 MOZ_KnownLive(geolocators[i])->NotifyError(aErrorCode);
591 }
592 return NS_OK;
593 }
594
SetCachedPosition(nsIDOMGeoPosition * aPosition)595 void nsGeolocationService::SetCachedPosition(nsIDOMGeoPosition* aPosition) {
596 mLastPosition.position = aPosition;
597 mLastPosition.isHighAccuracy = mHigherAccuracy;
598 }
599
GetCachedPosition()600 CachedPositionAndAccuracy nsGeolocationService::GetCachedPosition() {
601 return mLastPosition;
602 }
603
StartDevice(nsIPrincipal * aPrincipal)604 nsresult nsGeolocationService::StartDevice(nsIPrincipal* aPrincipal) {
605 if (!StaticPrefs::geo_enabled()) {
606 return NS_ERROR_NOT_AVAILABLE;
607 }
608
609 // We do not want to keep the geolocation devices online
610 // indefinitely.
611 // Close them down after a reasonable period of inactivivity.
612 SetDisconnectTimer();
613
614 if (XRE_IsContentProcess()) {
615 ContentChild* cpc = ContentChild::GetSingleton();
616 cpc->SendAddGeolocationListener(HighAccuracyRequested());
617 return NS_OK;
618 }
619
620 // Start them up!
621 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
622 if (!obs) {
623 return NS_ERROR_FAILURE;
624 }
625
626 if (!mProvider) {
627 return NS_ERROR_FAILURE;
628 }
629
630 nsresult rv;
631
632 if (NS_FAILED(rv = mProvider->Startup()) ||
633 NS_FAILED(rv = mProvider->Watch(this))) {
634 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE);
635 return rv;
636 }
637
638 obs->NotifyObservers(mProvider, "geolocation-device-events", u"starting");
639
640 return NS_OK;
641 }
642
SetDisconnectTimer()643 void nsGeolocationService::SetDisconnectTimer() {
644 if (!mDisconnectTimer) {
645 mDisconnectTimer = NS_NewTimer();
646 } else {
647 mDisconnectTimer->Cancel();
648 }
649
650 mDisconnectTimer->Init(this, StaticPrefs::geo_timeout(),
651 nsITimer::TYPE_ONE_SHOT);
652 }
653
HighAccuracyRequested()654 bool nsGeolocationService::HighAccuracyRequested() {
655 for (uint32_t i = 0; i < mGeolocators.Length(); i++) {
656 if (mGeolocators[i]->HighAccuracyRequested()) {
657 return true;
658 }
659 }
660
661 return false;
662 }
663
UpdateAccuracy(bool aForceHigh)664 void nsGeolocationService::UpdateAccuracy(bool aForceHigh) {
665 bool highRequired = aForceHigh || HighAccuracyRequested();
666
667 if (XRE_IsContentProcess()) {
668 ContentChild* cpc = ContentChild::GetSingleton();
669 if (cpc->IsAlive()) {
670 cpc->SendSetGeolocationHigherAccuracy(highRequired);
671 }
672
673 return;
674 }
675
676 mProvider->SetHighAccuracy(!mHigherAccuracy && highRequired);
677 mHigherAccuracy = highRequired;
678 }
679
StopDevice()680 void nsGeolocationService::StopDevice() {
681 if (mDisconnectTimer) {
682 mDisconnectTimer->Cancel();
683 mDisconnectTimer = nullptr;
684 }
685
686 if (XRE_IsContentProcess()) {
687 ContentChild* cpc = ContentChild::GetSingleton();
688 cpc->SendRemoveGeolocationListener();
689
690 return; // bail early
691 }
692
693 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
694 if (!obs) {
695 return;
696 }
697
698 if (!mProvider) {
699 return;
700 }
701
702 mHigherAccuracy = false;
703
704 mProvider->Shutdown();
705 obs->NotifyObservers(mProvider, "geolocation-device-events", u"shutdown");
706 }
707
708 StaticRefPtr<nsGeolocationService> nsGeolocationService::sService;
709
710 already_AddRefed<nsGeolocationService>
GetGeolocationService()711 nsGeolocationService::GetGeolocationService() {
712 RefPtr<nsGeolocationService> result;
713 if (nsGeolocationService::sService) {
714 result = nsGeolocationService::sService;
715
716 return result.forget();
717 }
718
719 result = new nsGeolocationService();
720 if (NS_FAILED(result->Init())) {
721 return nullptr;
722 }
723
724 ClearOnShutdown(&nsGeolocationService::sService);
725 nsGeolocationService::sService = result;
726 return result.forget();
727 }
728
AddLocator(Geolocation * aLocator)729 void nsGeolocationService::AddLocator(Geolocation* aLocator) {
730 mGeolocators.AppendElement(aLocator);
731 }
732
RemoveLocator(Geolocation * aLocator)733 void nsGeolocationService::RemoveLocator(Geolocation* aLocator) {
734 mGeolocators.RemoveElement(aLocator);
735 }
736
737 ////////////////////////////////////////////////////
738 // Geolocation
739 ////////////////////////////////////////////////////
740
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Geolocation)741 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Geolocation)
742 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
743 NS_INTERFACE_MAP_ENTRY(nsISupports)
744 NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate)
745 NS_INTERFACE_MAP_END
746
747 NS_IMPL_CYCLE_COLLECTING_ADDREF(Geolocation)
748 NS_IMPL_CYCLE_COLLECTING_RELEASE(Geolocation)
749
750 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Geolocation, mPendingCallbacks,
751 mWatchingCallbacks, mPendingRequests)
752
753 Geolocation::Geolocation()
754 : mProtocolType(ProtocolType::OTHER), mLastWatchId(0) {}
755
~Geolocation()756 Geolocation::~Geolocation() {
757 if (mService) {
758 Shutdown();
759 }
760 }
761
762 StaticRefPtr<Geolocation> Geolocation::sNonWindowSingleton;
763
NonWindowSingleton()764 already_AddRefed<Geolocation> Geolocation::NonWindowSingleton() {
765 if (sNonWindowSingleton) {
766 return do_AddRef(sNonWindowSingleton);
767 }
768
769 RefPtr<Geolocation> result = new Geolocation();
770 DebugOnly<nsresult> rv = result->Init();
771 MOZ_ASSERT(NS_SUCCEEDED(rv), "How can this fail?");
772
773 ClearOnShutdown(&sNonWindowSingleton);
774 sNonWindowSingleton = result;
775 return result.forget();
776 }
777
Init(nsPIDOMWindowInner * aContentDom)778 nsresult Geolocation::Init(nsPIDOMWindowInner* aContentDom) {
779 // Remember the window
780 if (aContentDom) {
781 mOwner = do_GetWeakReference(aContentDom);
782 if (!mOwner) {
783 return NS_ERROR_FAILURE;
784 }
785
786 // Grab the principal of the document
787 nsCOMPtr<Document> doc = aContentDom->GetDoc();
788 if (!doc) {
789 return NS_ERROR_FAILURE;
790 }
791
792 mPrincipal = doc->NodePrincipal();
793 // Store the protocol to send via telemetry later.
794 if (mPrincipal->SchemeIs("http")) {
795 mProtocolType = ProtocolType::HTTP;
796 } else if (mPrincipal->SchemeIs("https")) {
797 mProtocolType = ProtocolType::HTTPS;
798 }
799 }
800
801 // If no aContentDom was passed into us, we are being used
802 // by chrome/c++ and have no mOwner, no mPrincipal, and no need
803 // to prompt.
804 mService = nsGeolocationService::GetGeolocationService();
805 if (mService) {
806 mService->AddLocator(this);
807 }
808
809 return NS_OK;
810 }
811
Shutdown()812 void Geolocation::Shutdown() {
813 // Release all callbacks
814 mPendingCallbacks.Clear();
815 mWatchingCallbacks.Clear();
816
817 if (mService) {
818 mService->RemoveLocator(this);
819 mService->UpdateAccuracy();
820 }
821
822 mService = nullptr;
823 mPrincipal = nullptr;
824 }
825
GetParentObject() const826 nsPIDOMWindowInner* Geolocation::GetParentObject() const {
827 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mOwner);
828 return window.get();
829 }
830
HasActiveCallbacks()831 bool Geolocation::HasActiveCallbacks() {
832 return mPendingCallbacks.Length() || mWatchingCallbacks.Length();
833 }
834
HighAccuracyRequested()835 bool Geolocation::HighAccuracyRequested() {
836 for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
837 if (mWatchingCallbacks[i]->WantsHighAccuracy()) {
838 return true;
839 }
840 }
841
842 for (uint32_t i = 0; i < mPendingCallbacks.Length(); i++) {
843 if (mPendingCallbacks[i]->WantsHighAccuracy()) {
844 return true;
845 }
846 }
847
848 return false;
849 }
850
RemoveRequest(nsGeolocationRequest * aRequest)851 void Geolocation::RemoveRequest(nsGeolocationRequest* aRequest) {
852 bool requestWasKnown = (mPendingCallbacks.RemoveElement(aRequest) !=
853 mWatchingCallbacks.RemoveElement(aRequest));
854
855 Unused << requestWasKnown;
856 }
857
858 NS_IMETHODIMP
Update(nsIDOMGeoPosition * aSomewhere)859 Geolocation::Update(nsIDOMGeoPosition* aSomewhere) {
860 if (!WindowOwnerStillExists()) {
861 Shutdown();
862 return NS_OK;
863 }
864
865 if (aSomewhere) {
866 nsCOMPtr<nsIDOMGeoPositionCoords> coords;
867 aSomewhere->GetCoords(getter_AddRefs(coords));
868 if (coords) {
869 double accuracy = -1;
870 coords->GetAccuracy(&accuracy);
871 mozilla::Telemetry::Accumulate(
872 mozilla::Telemetry::GEOLOCATION_ACCURACY_EXPONENTIAL, accuracy);
873 }
874 }
875
876 for (uint32_t i = mPendingCallbacks.Length(); i > 0; i--) {
877 mPendingCallbacks[i - 1]->Update(aSomewhere);
878 RemoveRequest(mPendingCallbacks[i - 1]);
879 }
880
881 // notify everyone that is watching
882 for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
883 mWatchingCallbacks[i]->Update(aSomewhere);
884 }
885
886 return NS_OK;
887 }
888
889 NS_IMETHODIMP
NotifyError(uint16_t aErrorCode)890 Geolocation::NotifyError(uint16_t aErrorCode) {
891 if (!WindowOwnerStillExists()) {
892 Shutdown();
893 return NS_OK;
894 }
895
896 mozilla::Telemetry::Accumulate(mozilla::Telemetry::GEOLOCATION_ERROR, true);
897
898 for (uint32_t i = mPendingCallbacks.Length(); i > 0; i--) {
899 RefPtr<nsGeolocationRequest> request = mPendingCallbacks[i - 1];
900 request->NotifyErrorAndShutdown(aErrorCode);
901 // NotifyErrorAndShutdown() removes the request from the array
902 }
903
904 // notify everyone that is watching
905 for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
906 RefPtr<nsGeolocationRequest> request = mWatchingCallbacks[i];
907 request->NotifyErrorAndShutdown(aErrorCode);
908 }
909
910 return NS_OK;
911 }
912
IsAlreadyCleared(nsGeolocationRequest * aRequest)913 bool Geolocation::IsAlreadyCleared(nsGeolocationRequest* aRequest) {
914 for (uint32_t i = 0, length = mClearedWatchIDs.Length(); i < length; ++i) {
915 if (mClearedWatchIDs[i] == aRequest->WatchId()) {
916 return true;
917 }
918 }
919
920 return false;
921 }
922
ShouldBlockInsecureRequests() const923 bool Geolocation::ShouldBlockInsecureRequests() const {
924 if (Preferences::GetBool(PREF_GEO_SECURITY_ALLOWINSECURE, false)) {
925 return false;
926 }
927
928 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryReferent(mOwner);
929 if (!win) {
930 return false;
931 }
932
933 nsCOMPtr<Document> doc = win->GetDoc();
934 if (!doc) {
935 return false;
936 }
937
938 if (!nsGlobalWindowInner::Cast(win)->IsSecureContext()) {
939 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns, doc,
940 nsContentUtils::eDOM_PROPERTIES,
941 "GeolocationInsecureRequestIsForbidden");
942 return true;
943 }
944
945 return false;
946 }
947
ClearPendingRequest(nsGeolocationRequest * aRequest)948 bool Geolocation::ClearPendingRequest(nsGeolocationRequest* aRequest) {
949 if (aRequest->IsWatch() && this->IsAlreadyCleared(aRequest)) {
950 this->NotifyAllowedRequest(aRequest);
951 this->ClearWatch(aRequest->WatchId());
952 return true;
953 }
954
955 return false;
956 }
957
GetCurrentPosition(PositionCallback & aCallback,PositionErrorCallback * aErrorCallback,const PositionOptions & aOptions,CallerType aCallerType,ErrorResult & aRv)958 void Geolocation::GetCurrentPosition(PositionCallback& aCallback,
959 PositionErrorCallback* aErrorCallback,
960 const PositionOptions& aOptions,
961 CallerType aCallerType, ErrorResult& aRv) {
962 nsresult rv = GetCurrentPosition(
963 GeoPositionCallback(&aCallback), GeoPositionErrorCallback(aErrorCallback),
964 CreatePositionOptionsCopy(aOptions), aCallerType);
965
966 if (NS_FAILED(rv)) {
967 aRv.Throw(rv);
968 }
969 }
970
MainThreadTarget(Geolocation * geo)971 static nsIEventTarget* MainThreadTarget(Geolocation* geo) {
972 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(geo->GetOwner());
973 if (!window) {
974 return GetMainThreadEventTarget();
975 }
976 return nsGlobalWindowInner::Cast(window)->EventTargetFor(
977 mozilla::TaskCategory::Other);
978 }
979
GetCurrentPosition(GeoPositionCallback callback,GeoPositionErrorCallback errorCallback,UniquePtr<PositionOptions> && options,CallerType aCallerType)980 nsresult Geolocation::GetCurrentPosition(GeoPositionCallback callback,
981 GeoPositionErrorCallback errorCallback,
982 UniquePtr<PositionOptions>&& options,
983 CallerType aCallerType) {
984 if (mPendingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) {
985 return NS_ERROR_NOT_AVAILABLE;
986 }
987
988 // After this we hand over ownership of options to our nsGeolocationRequest.
989
990 nsIEventTarget* target = MainThreadTarget(this);
991 RefPtr<nsGeolocationRequest> request = new nsGeolocationRequest(
992 this, std::move(callback), std::move(errorCallback), std::move(options),
993 target);
994
995 if (!StaticPrefs::geo_enabled() || ShouldBlockInsecureRequests() ||
996 !request->CheckPermissionDelegate()) {
997 request->RequestDelayedTask(target,
998 nsGeolocationRequest::DelayedTaskType::Deny);
999 return NS_OK;
1000 }
1001
1002 if (!mOwner && aCallerType != CallerType::System) {
1003 return NS_ERROR_FAILURE;
1004 }
1005
1006 if (mOwner) {
1007 if (!RegisterRequestWithPrompt(request)) {
1008 return NS_ERROR_NOT_AVAILABLE;
1009 }
1010
1011 return NS_OK;
1012 }
1013
1014 if (aCallerType != CallerType::System) {
1015 return NS_ERROR_FAILURE;
1016 }
1017
1018 request->RequestDelayedTask(target,
1019 nsGeolocationRequest::DelayedTaskType::Allow);
1020
1021 return NS_OK;
1022 }
1023
WatchPosition(PositionCallback & aCallback,PositionErrorCallback * aErrorCallback,const PositionOptions & aOptions,CallerType aCallerType,ErrorResult & aRv)1024 int32_t Geolocation::WatchPosition(PositionCallback& aCallback,
1025 PositionErrorCallback* aErrorCallback,
1026 const PositionOptions& aOptions,
1027 CallerType aCallerType, ErrorResult& aRv) {
1028 return WatchPosition(GeoPositionCallback(&aCallback),
1029 GeoPositionErrorCallback(aErrorCallback),
1030 CreatePositionOptionsCopy(aOptions), aCallerType, aRv);
1031 }
1032
WatchPosition(nsIDOMGeoPositionCallback * aCallback,nsIDOMGeoPositionErrorCallback * aErrorCallback,UniquePtr<PositionOptions> && aOptions)1033 int32_t Geolocation::WatchPosition(
1034 nsIDOMGeoPositionCallback* aCallback,
1035 nsIDOMGeoPositionErrorCallback* aErrorCallback,
1036 UniquePtr<PositionOptions>&& aOptions) {
1037 MOZ_ASSERT(aCallback);
1038
1039 return WatchPosition(GeoPositionCallback(aCallback),
1040 GeoPositionErrorCallback(aErrorCallback),
1041 std::move(aOptions), CallerType::System, IgnoreErrors());
1042 }
1043
1044 // On errors we return -1 because that's not a valid watch id and will
1045 // get ignored in clearWatch.
WatchPosition(GeoPositionCallback aCallback,GeoPositionErrorCallback aErrorCallback,UniquePtr<PositionOptions> && aOptions,CallerType aCallerType,ErrorResult & aRv)1046 int32_t Geolocation::WatchPosition(GeoPositionCallback aCallback,
1047 GeoPositionErrorCallback aErrorCallback,
1048 UniquePtr<PositionOptions>&& aOptions,
1049 CallerType aCallerType, ErrorResult& aRv) {
1050 if (mWatchingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) {
1051 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
1052 return -1;
1053 }
1054
1055 // The watch ID:
1056 int32_t watchId = mLastWatchId++;
1057
1058 nsIEventTarget* target = MainThreadTarget(this);
1059 RefPtr<nsGeolocationRequest> request = new nsGeolocationRequest(
1060 this, std::move(aCallback), std::move(aErrorCallback),
1061 std::move(aOptions), target, true, watchId);
1062
1063 if (!StaticPrefs::geo_enabled() || ShouldBlockInsecureRequests() ||
1064 !request->CheckPermissionDelegate()) {
1065 request->RequestDelayedTask(target,
1066 nsGeolocationRequest::DelayedTaskType::Deny);
1067 return watchId;
1068 }
1069
1070 if (!mOwner && aCallerType != CallerType::System) {
1071 aRv.Throw(NS_ERROR_FAILURE);
1072 return -1;
1073 }
1074
1075 if (mOwner) {
1076 if (!RegisterRequestWithPrompt(request)) {
1077 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
1078 return -1;
1079 }
1080
1081 return watchId;
1082 }
1083
1084 if (aCallerType != CallerType::System) {
1085 aRv.Throw(NS_ERROR_FAILURE);
1086 return -1;
1087 }
1088
1089 request->Allow(JS::UndefinedHandleValue);
1090 return watchId;
1091 }
1092
ClearWatch(int32_t aWatchId)1093 void Geolocation::ClearWatch(int32_t aWatchId) {
1094 if (aWatchId < 0) {
1095 return;
1096 }
1097
1098 if (!mClearedWatchIDs.Contains(aWatchId)) {
1099 mClearedWatchIDs.AppendElement(aWatchId);
1100 }
1101
1102 for (uint32_t i = 0, length = mWatchingCallbacks.Length(); i < length; ++i) {
1103 if (mWatchingCallbacks[i]->WatchId() == aWatchId) {
1104 mWatchingCallbacks[i]->Shutdown();
1105 RemoveRequest(mWatchingCallbacks[i]);
1106 mClearedWatchIDs.RemoveElement(aWatchId);
1107 break;
1108 }
1109 }
1110
1111 // make sure we also search through the pending requests lists for
1112 // watches to clear...
1113 for (uint32_t i = 0, length = mPendingRequests.Length(); i < length; ++i) {
1114 if (mPendingRequests[i]->IsWatch() &&
1115 (mPendingRequests[i]->WatchId() == aWatchId)) {
1116 mPendingRequests[i]->Shutdown();
1117 mPendingRequests.RemoveElementAt(i);
1118 break;
1119 }
1120 }
1121 }
1122
WindowOwnerStillExists()1123 bool Geolocation::WindowOwnerStillExists() {
1124 // an owner was never set when Geolocation
1125 // was created, which means that this object
1126 // is being used without a window.
1127 if (mOwner == nullptr) {
1128 return true;
1129 }
1130
1131 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mOwner);
1132
1133 if (window) {
1134 nsPIDOMWindowOuter* outer = window->GetOuterWindow();
1135 if (!outer || outer->GetCurrentInnerWindow() != window || outer->Closed()) {
1136 return false;
1137 }
1138 }
1139
1140 return true;
1141 }
1142
NotifyAllowedRequest(nsGeolocationRequest * aRequest)1143 void Geolocation::NotifyAllowedRequest(nsGeolocationRequest* aRequest) {
1144 if (aRequest->IsWatch()) {
1145 mWatchingCallbacks.AppendElement(aRequest);
1146 } else {
1147 mPendingCallbacks.AppendElement(aRequest);
1148 }
1149 }
1150
RegisterRequestWithPrompt(nsGeolocationRequest * request)1151 bool Geolocation::RegisterRequestWithPrompt(nsGeolocationRequest* request) {
1152 nsIEventTarget* target = MainThreadTarget(this);
1153 ContentPermissionRequestBase::PromptResult pr = request->CheckPromptPrefs();
1154 if (pr == ContentPermissionRequestBase::PromptResult::Granted) {
1155 request->RequestDelayedTask(target,
1156 nsGeolocationRequest::DelayedTaskType::Allow);
1157 return true;
1158 }
1159 if (pr == ContentPermissionRequestBase::PromptResult::Denied) {
1160 request->RequestDelayedTask(target,
1161 nsGeolocationRequest::DelayedTaskType::Deny);
1162 return true;
1163 }
1164
1165 request->RequestDelayedTask(target,
1166 nsGeolocationRequest::DelayedTaskType::Request);
1167 return true;
1168 }
1169
WrapObject(JSContext * aCtx,JS::Handle<JSObject * > aGivenProto)1170 JSObject* Geolocation::WrapObject(JSContext* aCtx,
1171 JS::Handle<JSObject*> aGivenProto) {
1172 return Geolocation_Binding::Wrap(aCtx, this, aGivenProto);
1173 }
1174