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 "nsXULAppAPI.h"
8 
9 #include "mozilla/dom/ContentChild.h"
10 #include "mozilla/Telemetry.h"
11 #include "mozilla/UniquePtr.h"
12 
13 #include "nsGeolocation.h"
14 #include "nsDOMClassInfoID.h"
15 #include "nsComponentManagerUtils.h"
16 #include "nsServiceManagerUtils.h"
17 #include "nsContentUtils.h"
18 #include "nsContentPermissionHelper.h"
19 #include "nsIDocument.h"
20 #include "nsIObserverService.h"
21 #include "nsPIDOMWindow.h"
22 #include "nsThreadUtils.h"
23 #include "mozilla/Services.h"
24 #include "mozilla/Unused.h"
25 #include "mozilla/Preferences.h"
26 #include "mozilla/ClearOnShutdown.h"
27 #include "mozilla/WeakPtr.h"
28 #include "mozilla/dom/PermissionMessageUtils.h"
29 
30 class nsIPrincipal;
31 
32 #ifdef MOZ_WIDGET_ANDROID
33 #include "AndroidLocationProvider.h"
34 #endif
35 
36 #ifdef MOZ_WIDGET_GONK
37 #include "GonkGPSGeolocationProvider.h"
38 #endif
39 
40 #ifdef MOZ_GPSD
41 #include "GpsdLocationProvider.h"
42 #endif
43 
44 #ifdef MOZ_WIDGET_COCOA
45 #include "CoreLocationLocationProvider.h"
46 #endif
47 
48 #ifdef XP_WIN
49 #include "WindowsLocationProvider.h"
50 #include "mozilla/WindowsVersion.h"
51 #endif
52 
53 // Some limit to the number of get or watch geolocation requests
54 // that a window can make.
55 #define MAX_GEO_REQUESTS_PER_WINDOW  1500
56 
57 using mozilla::Unused;          // <snicker>
58 using namespace mozilla;
59 using namespace mozilla::dom;
60 
61 class nsGeolocationRequest final
62  : public nsIContentPermissionRequest
63  , public nsIGeolocationUpdate
64  , public SupportsWeakPtr<nsGeolocationRequest>
65 {
66  public:
67   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
68   NS_DECL_NSICONTENTPERMISSIONREQUEST
69   NS_DECL_NSIGEOLOCATIONUPDATE
70 
71   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsGeolocationRequest, nsIContentPermissionRequest)
72 
73   nsGeolocationRequest(Geolocation* aLocator,
74                        GeoPositionCallback aCallback,
75                        GeoPositionErrorCallback aErrorCallback,
76                        UniquePtr<PositionOptions>&& aOptions,
77                        uint8_t aProtocolType,
78                        bool aWatchPositionRequest = false,
79                        int32_t aWatchId = 0);
80 
81   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsGeolocationRequest)
82 
83   void Shutdown();
84 
85   void SendLocation(nsIDOMGeoPosition* aLocation);
WantsHighAccuracy()86   bool WantsHighAccuracy() {return !mShutdown && mOptions && mOptions->mEnableHighAccuracy;}
87   void SetTimeoutTimer();
88   void StopTimeoutTimer();
89   void NotifyErrorAndShutdown(uint16_t);
90   nsIPrincipal* GetPrincipal();
91 
IsWatch()92   bool IsWatch() { return mIsWatchPositionRequest; }
WatchId()93   int32_t WatchId() { return mWatchId; }
94  private:
95   virtual ~nsGeolocationRequest();
96 
97   class TimerCallbackHolder final : public nsITimerCallback
98   {
99   public:
100     NS_DECL_ISUPPORTS
101     NS_DECL_NSITIMERCALLBACK
102 
TimerCallbackHolder(nsGeolocationRequest * aRequest)103     explicit TimerCallbackHolder(nsGeolocationRequest* aRequest)
104       : mRequest(aRequest)
105     {}
106 
107   private:
~TimerCallbackHolder()108     ~TimerCallbackHolder() {}
109     WeakPtr<nsGeolocationRequest> mRequest;
110   };
111 
112   void Notify();
113 
114   bool mIsWatchPositionRequest;
115 
116   nsCOMPtr<nsITimer> mTimeoutTimer;
117   GeoPositionCallback mCallback;
118   GeoPositionErrorCallback mErrorCallback;
119   UniquePtr<PositionOptions> mOptions;
120 
121   RefPtr<Geolocation> mLocator;
122 
123   int32_t mWatchId;
124   bool mShutdown;
125   nsCOMPtr<nsIContentPermissionRequester> mRequester;
126   uint8_t mProtocolType;
127 };
128 
129 static UniquePtr<PositionOptions>
CreatePositionOptionsCopy(const PositionOptions & aOptions)130 CreatePositionOptionsCopy(const PositionOptions& aOptions)
131 {
132   UniquePtr<PositionOptions> geoOptions = MakeUnique<PositionOptions>();
133 
134   geoOptions->mEnableHighAccuracy = aOptions.mEnableHighAccuracy;
135   geoOptions->mMaximumAge = aOptions.mMaximumAge;
136   geoOptions->mTimeout = aOptions.mTimeout;
137 
138   return geoOptions;
139 }
140 
141 class RequestPromptEvent : public Runnable
142 {
143 public:
RequestPromptEvent(nsGeolocationRequest * aRequest,nsWeakPtr aWindow)144   RequestPromptEvent(nsGeolocationRequest* aRequest, nsWeakPtr aWindow)
145     : mRequest(aRequest)
146     , mWindow(aWindow)
147   {
148   }
149 
Run()150   NS_IMETHOD Run() override
151   {
152     nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mWindow);
153     nsContentPermissionUtils::AskPermission(mRequest, window);
154     return NS_OK;
155   }
156 
157 private:
158   RefPtr<nsGeolocationRequest> mRequest;
159   nsWeakPtr mWindow;
160 };
161 
162 class RequestAllowEvent : public Runnable
163 {
164 public:
RequestAllowEvent(int allow,nsGeolocationRequest * request)165   RequestAllowEvent(int allow, nsGeolocationRequest* request)
166     : mAllow(allow),
167       mRequest(request)
168   {
169   }
170 
Run()171   NS_IMETHOD Run() override {
172     if (mAllow) {
173       mRequest->Allow(JS::UndefinedHandleValue);
174     } else {
175       mRequest->Cancel();
176     }
177     return NS_OK;
178   }
179 
180 private:
181   bool mAllow;
182   RefPtr<nsGeolocationRequest> mRequest;
183 };
184 
185 class RequestSendLocationEvent : public Runnable
186 {
187 public:
RequestSendLocationEvent(nsIDOMGeoPosition * aPosition,nsGeolocationRequest * aRequest)188   RequestSendLocationEvent(nsIDOMGeoPosition* aPosition,
189                            nsGeolocationRequest* aRequest)
190     : mPosition(aPosition),
191       mRequest(aRequest)
192   {
193   }
194 
Run()195   NS_IMETHOD Run() override {
196     mRequest->SendLocation(mPosition);
197     return NS_OK;
198   }
199 
200 private:
201   nsCOMPtr<nsIDOMGeoPosition> mPosition;
202   RefPtr<nsGeolocationRequest> mRequest;
203   RefPtr<Geolocation> mLocator;
204 };
205 
206 ////////////////////////////////////////////////////
207 // PositionError
208 ////////////////////////////////////////////////////
209 
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PositionError)210 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PositionError)
211   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
212   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMGeoPositionError)
213   NS_INTERFACE_MAP_ENTRY(nsIDOMGeoPositionError)
214 NS_INTERFACE_MAP_END
215 
216 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PositionError, mParent)
217 NS_IMPL_CYCLE_COLLECTING_ADDREF(PositionError)
218 NS_IMPL_CYCLE_COLLECTING_RELEASE(PositionError)
219 
220 PositionError::PositionError(Geolocation* aParent, int16_t aCode)
221   : mCode(aCode)
222   , mParent(aParent)
223 {
224 }
225 
~PositionError()226 PositionError::~PositionError(){}
227 
228 
229 NS_IMETHODIMP
GetCode(int16_t * aCode)230 PositionError::GetCode(int16_t *aCode)
231 {
232   NS_ENSURE_ARG_POINTER(aCode);
233   *aCode = Code();
234   return NS_OK;
235 }
236 
237 NS_IMETHODIMP
GetMessage(nsAString & aMessage)238 PositionError::GetMessage(nsAString& aMessage)
239 {
240   switch (mCode)
241   {
242     case nsIDOMGeoPositionError::PERMISSION_DENIED:
243       aMessage = NS_LITERAL_STRING("User denied geolocation prompt");
244       break;
245     case nsIDOMGeoPositionError::POSITION_UNAVAILABLE:
246       aMessage = NS_LITERAL_STRING("Unknown error acquiring position");
247       break;
248     case nsIDOMGeoPositionError::TIMEOUT:
249       aMessage = NS_LITERAL_STRING("Position acquisition timed out");
250       break;
251     default:
252       break;
253   }
254   return NS_OK;
255 }
256 
257 Geolocation*
GetParentObject() const258 PositionError::GetParentObject() const
259 {
260   return mParent;
261 }
262 
263 JSObject*
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)264 PositionError::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
265 {
266   return PositionErrorBinding::Wrap(aCx, this, aGivenProto);
267 }
268 
269 void
NotifyCallback(const GeoPositionErrorCallback & aCallback)270 PositionError::NotifyCallback(const GeoPositionErrorCallback& aCallback)
271 {
272   nsAutoMicroTask mt;
273   if (aCallback.HasWebIDLCallback()) {
274     PositionErrorCallback* callback = aCallback.GetWebIDLCallback();
275 
276     if (callback) {
277       callback->Call(*this);
278     }
279   } else {
280     nsIDOMGeoPositionErrorCallback* callback = aCallback.GetXPCOMCallback();
281     if (callback) {
282       callback->HandleEvent(this);
283     }
284   }
285 }
286 ////////////////////////////////////////////////////
287 // nsGeolocationRequest
288 ////////////////////////////////////////////////////
289 
nsGeolocationRequest(Geolocation * aLocator,GeoPositionCallback aCallback,GeoPositionErrorCallback aErrorCallback,UniquePtr<PositionOptions> && aOptions,uint8_t aProtocolType,bool aWatchPositionRequest,int32_t aWatchId)290 nsGeolocationRequest::nsGeolocationRequest(Geolocation* aLocator,
291                                            GeoPositionCallback aCallback,
292                                            GeoPositionErrorCallback aErrorCallback,
293                                            UniquePtr<PositionOptions>&& aOptions,
294                                            uint8_t aProtocolType,
295                                            bool aWatchPositionRequest,
296                                            int32_t aWatchId)
297   : mIsWatchPositionRequest(aWatchPositionRequest),
298     mCallback(Move(aCallback)),
299     mErrorCallback(Move(aErrorCallback)),
300     mOptions(Move(aOptions)),
301     mLocator(aLocator),
302     mWatchId(aWatchId),
303     mShutdown(false),
304     mProtocolType(aProtocolType)
305 {
306   if (nsCOMPtr<nsPIDOMWindowInner> win =
307       do_QueryReferent(mLocator->GetOwner())) {
308     mRequester = new nsContentPermissionRequester(win);
309   }
310 }
311 
~nsGeolocationRequest()312 nsGeolocationRequest::~nsGeolocationRequest()
313 {
314   StopTimeoutTimer();
315 }
316 
317 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGeolocationRequest)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,nsIContentPermissionRequest)318   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
319   NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
320   NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate)
321 NS_INTERFACE_MAP_END
322 
323 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGeolocationRequest)
324 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGeolocationRequest)
325 NS_IMPL_CYCLE_COLLECTION(nsGeolocationRequest, mCallback, mErrorCallback, mLocator)
326 
327 void
328 nsGeolocationRequest::Notify()
329 {
330   SetTimeoutTimer();
331   NotifyErrorAndShutdown(nsIDOMGeoPositionError::TIMEOUT);
332 }
333 
334 void
NotifyErrorAndShutdown(uint16_t aErrorCode)335 nsGeolocationRequest::NotifyErrorAndShutdown(uint16_t aErrorCode)
336 {
337   MOZ_ASSERT(!mShutdown, "timeout after shutdown");
338   if (!mIsWatchPositionRequest) {
339     Shutdown();
340     mLocator->RemoveRequest(this);
341   }
342 
343   NotifyError(aErrorCode);
344 }
345 
346 NS_IMETHODIMP
GetPrincipal(nsIPrincipal ** aRequestingPrincipal)347 nsGeolocationRequest::GetPrincipal(nsIPrincipal * *aRequestingPrincipal)
348 {
349   NS_ENSURE_ARG_POINTER(aRequestingPrincipal);
350 
351   nsCOMPtr<nsIPrincipal> principal = mLocator->GetPrincipal();
352   principal.forget(aRequestingPrincipal);
353 
354   return NS_OK;
355 }
356 
357 NS_IMETHODIMP
GetTypes(nsIArray ** aTypes)358 nsGeolocationRequest::GetTypes(nsIArray** aTypes)
359 {
360   nsTArray<nsString> emptyOptions;
361   return nsContentPermissionUtils::CreatePermissionArray(NS_LITERAL_CSTRING("geolocation"),
362                                                          NS_LITERAL_CSTRING("unused"),
363                                                          emptyOptions,
364                                                          aTypes);
365 }
366 
367 NS_IMETHODIMP
GetWindow(mozIDOMWindow ** aRequestingWindow)368 nsGeolocationRequest::GetWindow(mozIDOMWindow** aRequestingWindow)
369 {
370   NS_ENSURE_ARG_POINTER(aRequestingWindow);
371 
372   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mLocator->GetOwner());
373   window.forget(aRequestingWindow);
374 
375   return NS_OK;
376 }
377 
378 NS_IMETHODIMP
GetElement(nsIDOMElement ** aRequestingElement)379 nsGeolocationRequest::GetElement(nsIDOMElement * *aRequestingElement)
380 {
381   NS_ENSURE_ARG_POINTER(aRequestingElement);
382   *aRequestingElement = nullptr;
383   return NS_OK;
384 }
385 
386 NS_IMETHODIMP
Cancel()387 nsGeolocationRequest::Cancel()
388 {
389   if (mRequester) {
390     // Record the number of denied requests for regular web content.
391     // This method is only called when the user explicitly denies the request,
392     // and is not called when the page is simply unloaded, or similar.
393     Telemetry::Accumulate(Telemetry::GEOLOCATION_REQUEST_GRANTED, mProtocolType);
394   }
395 
396   if (mLocator->ClearPendingRequest(this)) {
397     return NS_OK;
398   }
399 
400   NotifyError(nsIDOMGeoPositionError::PERMISSION_DENIED);
401   return NS_OK;
402 }
403 
404 NS_IMETHODIMP
Allow(JS::HandleValue aChoices)405 nsGeolocationRequest::Allow(JS::HandleValue aChoices)
406 {
407   MOZ_ASSERT(aChoices.isUndefined());
408 
409   if (mRequester) {
410     // Record the number of granted requests for regular web content.
411     Telemetry::Accumulate(Telemetry::GEOLOCATION_REQUEST_GRANTED, mProtocolType + 10);
412 
413     // Record whether a location callback is fulfilled while the owner window
414     // is not visible.
415     bool isVisible = false;
416     nsCOMPtr<nsPIDOMWindowInner> window = mLocator->GetParentObject();
417 
418     if (window) {
419       nsCOMPtr<nsIDocument> doc = window->GetDoc();
420       isVisible = doc && !doc->Hidden();
421     }
422 
423     if (IsWatch()) {
424       mozilla::Telemetry::Accumulate(mozilla::Telemetry::GEOLOCATION_WATCHPOSITION_VISIBLE, isVisible);
425     } else {
426       mozilla::Telemetry::Accumulate(mozilla::Telemetry::GEOLOCATION_GETCURRENTPOSITION_VISIBLE, isVisible);
427     }
428   }
429 
430   if (mLocator->ClearPendingRequest(this)) {
431     return NS_OK;
432   }
433 
434   RefPtr<nsGeolocationService> gs = nsGeolocationService::GetGeolocationService();
435 
436   bool canUseCache = false;
437   CachedPositionAndAccuracy lastPosition = gs->GetCachedPosition();
438   if (lastPosition.position) {
439     DOMTimeStamp cachedPositionTime_ms;
440     lastPosition.position->GetTimestamp(&cachedPositionTime_ms);
441     // check to see if we can use a cached value
442     // if the user has specified a maximumAge, return a cached value.
443     if (mOptions && mOptions->mMaximumAge > 0) {
444       uint32_t maximumAge_ms = mOptions->mMaximumAge;
445       bool isCachedWithinRequestedAccuracy = WantsHighAccuracy() <= lastPosition.isHighAccuracy;
446       bool isCachedWithinRequestedTime =
447         DOMTimeStamp(PR_Now() / PR_USEC_PER_MSEC - maximumAge_ms) <= cachedPositionTime_ms;
448       canUseCache = isCachedWithinRequestedAccuracy && isCachedWithinRequestedTime;
449     }
450   }
451 
452   gs->UpdateAccuracy(WantsHighAccuracy());
453   if (canUseCache) {
454     // okay, we can return a cached position
455     // getCurrentPosition requests serviced by the cache
456     // will now be owned by the RequestSendLocationEvent
457     Update(lastPosition.position);
458 
459     // After Update is called, getCurrentPosition finishes it's job.
460     if (!mIsWatchPositionRequest) {
461       return NS_OK;
462     }
463 
464   } else {
465     // if it is not a watch request and timeout is 0,
466     // invoke the errorCallback (if present) with TIMEOUT code
467     if (mOptions && mOptions->mTimeout == 0 && !mIsWatchPositionRequest) {
468       NotifyError(nsIDOMGeoPositionError::TIMEOUT);
469       return NS_OK;
470     }
471 
472   }
473 
474   // Kick off the geo device, if it isn't already running
475   nsresult rv = gs->StartDevice(GetPrincipal());
476 
477   if (NS_FAILED(rv)) {
478     // Location provider error
479     NotifyError(nsIDOMGeoPositionError::POSITION_UNAVAILABLE);
480     return NS_OK;
481   }
482 
483   if (mIsWatchPositionRequest || !canUseCache) {
484     // let the locator know we're pending
485     // we will now be owned by the locator
486     mLocator->NotifyAllowedRequest(this);
487   }
488 
489   SetTimeoutTimer();
490 
491   return NS_OK;
492 }
493 
494 NS_IMETHODIMP
GetRequester(nsIContentPermissionRequester ** aRequester)495 nsGeolocationRequest::GetRequester(nsIContentPermissionRequester** aRequester)
496 {
497   NS_ENSURE_ARG_POINTER(aRequester);
498 
499   nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
500   requester.forget(aRequester);
501 
502   return NS_OK;
503 }
504 
505 void
SetTimeoutTimer()506 nsGeolocationRequest::SetTimeoutTimer()
507 {
508   MOZ_ASSERT(!mShutdown, "set timeout after shutdown");
509 
510   StopTimeoutTimer();
511 
512   if (mOptions && mOptions->mTimeout != 0 && mOptions->mTimeout != 0x7fffffff) {
513     mTimeoutTimer = do_CreateInstance("@mozilla.org/timer;1");
514     RefPtr<TimerCallbackHolder> holder = new TimerCallbackHolder(this);
515     mTimeoutTimer->InitWithCallback(holder, mOptions->mTimeout, nsITimer::TYPE_ONE_SHOT);
516   }
517 }
518 
519 void
StopTimeoutTimer()520 nsGeolocationRequest::StopTimeoutTimer()
521 {
522   if (mTimeoutTimer) {
523     mTimeoutTimer->Cancel();
524     mTimeoutTimer = nullptr;
525   }
526 }
527 
528 void
SendLocation(nsIDOMGeoPosition * aPosition)529 nsGeolocationRequest::SendLocation(nsIDOMGeoPosition* aPosition)
530 {
531   if (mShutdown) {
532     // Ignore SendLocationEvents issued before we were cleared.
533     return;
534   }
535 
536   if (mOptions && mOptions->mMaximumAge > 0) {
537     DOMTimeStamp positionTime_ms;
538     aPosition->GetTimestamp(&positionTime_ms);
539     const uint32_t maximumAge_ms = mOptions->mMaximumAge;
540     const bool isTooOld =
541         DOMTimeStamp(PR_Now() / PR_USEC_PER_MSEC - maximumAge_ms) > positionTime_ms;
542     if (isTooOld) {
543       return;
544     }
545   }
546 
547   RefPtr<Position> wrapped;
548 
549   if (aPosition) {
550     nsCOMPtr<nsIDOMGeoPositionCoords> coords;
551     aPosition->GetCoords(getter_AddRefs(coords));
552     if (coords) {
553       wrapped = new Position(ToSupports(mLocator), aPosition);
554     }
555   }
556 
557   if (!wrapped) {
558     NotifyError(nsIDOMGeoPositionError::POSITION_UNAVAILABLE);
559     return;
560   }
561 
562   if (!mIsWatchPositionRequest) {
563     // Cancel timer and position updates in case the position
564     // callback spins the event loop
565     Shutdown();
566   }
567 
568   nsAutoMicroTask mt;
569   if (mCallback.HasWebIDLCallback()) {
570     PositionCallback* callback = mCallback.GetWebIDLCallback();
571 
572     MOZ_ASSERT(callback);
573     callback->Call(*wrapped);
574   } else {
575     nsIDOMGeoPositionCallback* callback = mCallback.GetXPCOMCallback();
576     MOZ_ASSERT(callback);
577     callback->HandleEvent(aPosition);
578   }
579 
580   if (mIsWatchPositionRequest && !mShutdown) {
581     SetTimeoutTimer();
582   }
583   MOZ_ASSERT(mShutdown || mIsWatchPositionRequest,
584              "non-shutdown getCurrentPosition request after callback!");
585 }
586 
587 nsIPrincipal*
GetPrincipal()588 nsGeolocationRequest::GetPrincipal()
589 {
590   if (!mLocator) {
591     return nullptr;
592   }
593   return mLocator->GetPrincipal();
594 }
595 
596 NS_IMETHODIMP
Update(nsIDOMGeoPosition * aPosition)597 nsGeolocationRequest::Update(nsIDOMGeoPosition* aPosition)
598 {
599   nsCOMPtr<nsIRunnable> ev = new RequestSendLocationEvent(aPosition, this);
600   NS_DispatchToMainThread(ev);
601   return NS_OK;
602 }
603 
604 NS_IMETHODIMP
NotifyError(uint16_t aErrorCode)605 nsGeolocationRequest::NotifyError(uint16_t aErrorCode)
606 {
607   MOZ_ASSERT(NS_IsMainThread());
608   RefPtr<PositionError> positionError = new PositionError(mLocator, aErrorCode);
609   positionError->NotifyCallback(mErrorCallback);
610   return NS_OK;
611 }
612 
613 void
Shutdown()614 nsGeolocationRequest::Shutdown()
615 {
616   MOZ_ASSERT(!mShutdown, "request shutdown twice");
617   mShutdown = true;
618 
619   StopTimeoutTimer();
620 
621   // If there are no other high accuracy requests, the geolocation service will
622   // notify the provider to switch to the default accuracy.
623   if (mOptions && mOptions->mEnableHighAccuracy) {
624     RefPtr<nsGeolocationService> gs = nsGeolocationService::GetGeolocationService();
625     if (gs) {
626       gs->UpdateAccuracy();
627     }
628   }
629 }
630 
631 
632 ////////////////////////////////////////////////////
633 // nsGeolocationRequest::TimerCallbackHolder
634 ////////////////////////////////////////////////////
635 
NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder,nsISupports,nsITimerCallback)636 NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder, nsISupports, nsITimerCallback)
637 
638 NS_IMETHODIMP
639 nsGeolocationRequest::TimerCallbackHolder::Notify(nsITimer*)
640 {
641   if (mRequest && mRequest->mLocator) {
642     RefPtr<nsGeolocationRequest> request(mRequest);
643     request->Notify();
644   }
645 
646   return NS_OK;
647 }
648 
649 
650 ////////////////////////////////////////////////////
651 // nsGeolocationService
652 ////////////////////////////////////////////////////
653 NS_INTERFACE_MAP_BEGIN(nsGeolocationService)
654   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIGeolocationUpdate)
655   NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate)
656   NS_INTERFACE_MAP_ENTRY(nsIObserver)
657 NS_INTERFACE_MAP_END
658 
659 NS_IMPL_ADDREF(nsGeolocationService)
660 NS_IMPL_RELEASE(nsGeolocationService)
661 
662 
663 static bool sGeoEnabled = true;
664 static int32_t sProviderTimeout = 6000; // Time, in milliseconds, to wait for the location provider to spin up.
665 
Init()666 nsresult nsGeolocationService::Init()
667 {
668   Preferences::AddIntVarCache(&sProviderTimeout, "geo.timeout", sProviderTimeout);
669   Preferences::AddBoolVarCache(&sGeoEnabled, "geo.enabled", sGeoEnabled);
670 
671   if (!sGeoEnabled) {
672     return NS_ERROR_FAILURE;
673   }
674 
675   if (XRE_IsContentProcess()) {
676     return NS_OK;
677   }
678 
679   // geolocation service can be enabled -> now register observer
680   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
681   if (!obs) {
682     return NS_ERROR_FAILURE;
683   }
684 
685   obs->AddObserver(this, "xpcom-shutdown", false);
686 
687 #ifdef MOZ_WIDGET_ANDROID
688   mProvider = new AndroidLocationProvider();
689 #endif
690 
691 #ifdef MOZ_WIDGET_GONK
692   // GonkGPSGeolocationProvider can be started at boot up time for initialization reasons.
693   // do_getService gets hold of the already initialized component and starts
694   // processing location requests immediately.
695   // do_Createinstance will create multiple instances of the provider which is not right.
696   // bug 993041
697   mProvider = do_GetService(GONK_GPS_GEOLOCATION_PROVIDER_CONTRACTID);
698 #endif
699 
700 #ifdef MOZ_WIDGET_GTK
701 #ifdef MOZ_GPSD
702   if (Preferences::GetBool("geo.provider.use_gpsd", false)) {
703     mProvider = new GpsdLocationProvider();
704   }
705 #endif
706 #endif
707 
708 #ifdef MOZ_WIDGET_COCOA
709   if (Preferences::GetBool("geo.provider.use_corelocation", true)) {
710     mProvider = new CoreLocationLocationProvider();
711   }
712 #endif
713 
714 #ifdef XP_WIN
715   if (Preferences::GetBool("geo.provider.ms-windows-location", false) &&
716       IsWin8OrLater()) {
717     mProvider = new WindowsLocationProvider();
718   }
719 #endif
720 
721   if (Preferences::GetBool("geo.provider.use_mls", false)) {
722     mProvider = do_CreateInstance("@mozilla.org/geolocation/mls-provider;1");
723   }
724 
725   // Override platform-specific providers with the default (network)
726   // provider while testing. Our tests are currently not meant to exercise
727   // the provider, and some tests rely on the network provider being used.
728   // "geo.provider.testing" is always set for all plain and browser chrome
729   // mochitests, and also for xpcshell tests.
730   if (!mProvider || Preferences::GetBool("geo.provider.testing", false)) {
731     nsCOMPtr<nsIGeolocationProvider> override =
732       do_GetService(NS_GEOLOCATION_PROVIDER_CONTRACTID);
733 
734     if (override) {
735       mProvider = override;
736     }
737   }
738 
739   return NS_OK;
740 }
741 
~nsGeolocationService()742 nsGeolocationService::~nsGeolocationService()
743 {
744 }
745 
746 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)747 nsGeolocationService::Observe(nsISupports* aSubject,
748                               const char* aTopic,
749                               const char16_t* aData)
750 {
751   if (!strcmp("xpcom-shutdown", aTopic)) {
752     nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
753     if (obs) {
754       obs->RemoveObserver(this, "xpcom-shutdown");
755     }
756 
757     for (uint32_t i = 0; i< mGeolocators.Length(); i++) {
758       mGeolocators[i]->Shutdown();
759     }
760     StopDevice();
761 
762     return NS_OK;
763   }
764 
765   if (!strcmp("timer-callback", aTopic)) {
766     // decide if we can close down the service.
767     for (uint32_t i = 0; i< mGeolocators.Length(); i++)
768       if (mGeolocators[i]->HasActiveCallbacks()) {
769         SetDisconnectTimer();
770         return NS_OK;
771       }
772 
773     // okay to close up.
774     StopDevice();
775     Update(nullptr);
776     return NS_OK;
777   }
778 
779   return NS_ERROR_FAILURE;
780 }
781 
782 NS_IMETHODIMP
Update(nsIDOMGeoPosition * aSomewhere)783 nsGeolocationService::Update(nsIDOMGeoPosition *aSomewhere)
784 {
785   if (aSomewhere) {
786     SetCachedPosition(aSomewhere);
787   }
788 
789   for (uint32_t i = 0; i< mGeolocators.Length(); i++) {
790     mGeolocators[i]->Update(aSomewhere);
791   }
792 
793   return NS_OK;
794 }
795 
796 NS_IMETHODIMP
NotifyError(uint16_t aErrorCode)797 nsGeolocationService::NotifyError(uint16_t aErrorCode)
798 {
799   for (uint32_t i = 0; i < mGeolocators.Length(); i++) {
800     mGeolocators[i]->NotifyError(aErrorCode);
801   }
802   return NS_OK;
803 }
804 
805 void
SetCachedPosition(nsIDOMGeoPosition * aPosition)806 nsGeolocationService::SetCachedPosition(nsIDOMGeoPosition* aPosition)
807 {
808   mLastPosition.position = aPosition;
809   mLastPosition.isHighAccuracy = mHigherAccuracy;
810 }
811 
812 CachedPositionAndAccuracy
GetCachedPosition()813 nsGeolocationService::GetCachedPosition()
814 {
815   return mLastPosition;
816 }
817 
818 nsresult
StartDevice(nsIPrincipal * aPrincipal)819 nsGeolocationService::StartDevice(nsIPrincipal *aPrincipal)
820 {
821   if (!sGeoEnabled) {
822     return NS_ERROR_NOT_AVAILABLE;
823   }
824 
825   // We do not want to keep the geolocation devices online
826   // indefinitely.
827   // Close them down after a reasonable period of inactivivity.
828   SetDisconnectTimer();
829 
830   if (XRE_IsContentProcess()) {
831     ContentChild* cpc = ContentChild::GetSingleton();
832     cpc->SendAddGeolocationListener(IPC::Principal(aPrincipal),
833                                     HighAccuracyRequested());
834     return NS_OK;
835   }
836 
837   // Start them up!
838   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
839   if (!obs) {
840     return NS_ERROR_FAILURE;
841   }
842 
843   if (!mProvider) {
844     return NS_ERROR_FAILURE;
845   }
846 
847   nsresult rv;
848 
849   if (NS_FAILED(rv = mProvider->Startup()) ||
850       NS_FAILED(rv = mProvider->Watch(this))) {
851 
852     NotifyError(nsIDOMGeoPositionError::POSITION_UNAVAILABLE);
853     return rv;
854   }
855 
856   obs->NotifyObservers(mProvider,
857                        "geolocation-device-events",
858                        u"starting");
859 
860   return NS_OK;
861 }
862 
863 void
SetDisconnectTimer()864 nsGeolocationService::SetDisconnectTimer()
865 {
866   if (!mDisconnectTimer) {
867     mDisconnectTimer = do_CreateInstance("@mozilla.org/timer;1");
868   } else {
869     mDisconnectTimer->Cancel();
870   }
871 
872   mDisconnectTimer->Init(this,
873                          sProviderTimeout,
874                          nsITimer::TYPE_ONE_SHOT);
875 }
876 
877 bool
HighAccuracyRequested()878 nsGeolocationService::HighAccuracyRequested()
879 {
880   for (uint32_t i = 0; i < mGeolocators.Length(); i++) {
881     if (mGeolocators[i]->HighAccuracyRequested()) {
882       return true;
883     }
884   }
885 
886   return false;
887 }
888 
889 void
UpdateAccuracy(bool aForceHigh)890 nsGeolocationService::UpdateAccuracy(bool aForceHigh)
891 {
892   bool highRequired = aForceHigh || HighAccuracyRequested();
893 
894   if (XRE_IsContentProcess()) {
895     ContentChild* cpc = ContentChild::GetSingleton();
896     if (cpc->IsAlive()) {
897       cpc->SendSetGeolocationHigherAccuracy(highRequired);
898     }
899 
900     return;
901   }
902 
903   mProvider->SetHighAccuracy(!mHigherAccuracy && highRequired);
904   mHigherAccuracy = highRequired;
905 }
906 
907 void
StopDevice()908 nsGeolocationService::StopDevice()
909 {
910   if (mDisconnectTimer) {
911     mDisconnectTimer->Cancel();
912     mDisconnectTimer = nullptr;
913   }
914 
915   if (XRE_IsContentProcess()) {
916     ContentChild* cpc = ContentChild::GetSingleton();
917     cpc->SendRemoveGeolocationListener();
918 
919     return; // bail early
920   }
921 
922   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
923   if (!obs) {
924     return;
925   }
926 
927   if (!mProvider) {
928     return;
929   }
930 
931   mHigherAccuracy = false;
932 
933   mProvider->Shutdown();
934   obs->NotifyObservers(mProvider,
935                        "geolocation-device-events",
936                        u"shutdown");
937 }
938 
939 StaticRefPtr<nsGeolocationService> nsGeolocationService::sService;
940 
941 already_AddRefed<nsGeolocationService>
GetGeolocationService()942 nsGeolocationService::GetGeolocationService()
943 {
944   RefPtr<nsGeolocationService> result;
945   if (nsGeolocationService::sService) {
946     result = nsGeolocationService::sService;
947 
948     return result.forget();
949   }
950 
951   result = new nsGeolocationService();
952   if (NS_FAILED(result->Init())) {
953     return nullptr;
954   }
955 
956   ClearOnShutdown(&nsGeolocationService::sService);
957   nsGeolocationService::sService = result;
958   return result.forget();
959 }
960 
961 void
AddLocator(Geolocation * aLocator)962 nsGeolocationService::AddLocator(Geolocation* aLocator)
963 {
964   mGeolocators.AppendElement(aLocator);
965 }
966 
967 void
RemoveLocator(Geolocation * aLocator)968 nsGeolocationService::RemoveLocator(Geolocation* aLocator)
969 {
970   mGeolocators.RemoveElement(aLocator);
971 }
972 
973 ////////////////////////////////////////////////////
974 // Geolocation
975 ////////////////////////////////////////////////////
976 
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Geolocation)977 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Geolocation)
978   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
979   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMGeoGeolocation)
980   NS_INTERFACE_MAP_ENTRY(nsIDOMGeoGeolocation)
981   NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate)
982 NS_INTERFACE_MAP_END
983 
984 NS_IMPL_CYCLE_COLLECTING_ADDREF(Geolocation)
985 NS_IMPL_CYCLE_COLLECTING_RELEASE(Geolocation)
986 
987 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Geolocation,
988                                       mPendingCallbacks,
989                                       mWatchingCallbacks,
990                                       mPendingRequests)
991 
992 Geolocation::Geolocation()
993 : mProtocolType(ProtocolType::OTHER)
994 , mLastWatchId(0)
995 {
996 }
997 
~Geolocation()998 Geolocation::~Geolocation()
999 {
1000   if (mService) {
1001     Shutdown();
1002   }
1003 }
1004 
1005 nsresult
Init(nsPIDOMWindowInner * aContentDom)1006 Geolocation::Init(nsPIDOMWindowInner* aContentDom)
1007 {
1008   // Remember the window
1009   if (aContentDom) {
1010     mOwner = do_GetWeakReference(aContentDom);
1011     if (!mOwner) {
1012       return NS_ERROR_FAILURE;
1013     }
1014 
1015     // Grab the principal of the document
1016     nsCOMPtr<nsIDocument> doc = aContentDom->GetDoc();
1017     if (!doc) {
1018       return NS_ERROR_FAILURE;
1019     }
1020 
1021     mPrincipal = doc->NodePrincipal();
1022 
1023     nsCOMPtr<nsIURI> uri;
1024     nsresult rv = mPrincipal->GetURI(getter_AddRefs(uri));
1025     NS_ENSURE_SUCCESS(rv, rv);
1026 
1027     if (uri) {
1028       bool isHttp;
1029       rv = uri->SchemeIs("http", &isHttp);
1030       NS_ENSURE_SUCCESS(rv, rv);
1031 
1032       bool isHttps;
1033       rv = uri->SchemeIs("https", &isHttps);
1034       NS_ENSURE_SUCCESS(rv, rv);
1035 
1036       // Store the protocol to send via telemetry later.
1037       if (isHttp) {
1038         mProtocolType = ProtocolType::HTTP;
1039       } else if (isHttps) {
1040         mProtocolType = ProtocolType::HTTPS;
1041       }
1042     }
1043   }
1044 
1045   // If no aContentDom was passed into us, we are being used
1046   // by chrome/c++ and have no mOwner, no mPrincipal, and no need
1047   // to prompt.
1048   mService = nsGeolocationService::GetGeolocationService();
1049   if (mService) {
1050     mService->AddLocator(this);
1051   }
1052 
1053   return NS_OK;
1054 }
1055 
1056 void
Shutdown()1057 Geolocation::Shutdown()
1058 {
1059   // Release all callbacks
1060   mPendingCallbacks.Clear();
1061   mWatchingCallbacks.Clear();
1062 
1063   if (mService) {
1064     mService->RemoveLocator(this);
1065     mService->UpdateAccuracy();
1066   }
1067 
1068   mService = nullptr;
1069   mPrincipal = nullptr;
1070 }
1071 
1072 nsPIDOMWindowInner*
GetParentObject() const1073 Geolocation::GetParentObject() const {
1074   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mOwner);
1075   return window.get();
1076 }
1077 
1078 bool
HasActiveCallbacks()1079 Geolocation::HasActiveCallbacks()
1080 {
1081   return mPendingCallbacks.Length() || mWatchingCallbacks.Length();
1082 }
1083 
1084 bool
HighAccuracyRequested()1085 Geolocation::HighAccuracyRequested()
1086 {
1087   for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
1088     if (mWatchingCallbacks[i]->WantsHighAccuracy()) {
1089       return true;
1090     }
1091   }
1092 
1093   for (uint32_t i = 0; i < mPendingCallbacks.Length(); i++) {
1094     if (mPendingCallbacks[i]->WantsHighAccuracy()) {
1095       return true;
1096     }
1097   }
1098 
1099   return false;
1100 }
1101 
1102 void
RemoveRequest(nsGeolocationRequest * aRequest)1103 Geolocation::RemoveRequest(nsGeolocationRequest* aRequest)
1104 {
1105   bool requestWasKnown =
1106     (mPendingCallbacks.RemoveElement(aRequest) !=
1107      mWatchingCallbacks.RemoveElement(aRequest));
1108 
1109   Unused << requestWasKnown;
1110 }
1111 
1112 NS_IMETHODIMP
Update(nsIDOMGeoPosition * aSomewhere)1113 Geolocation::Update(nsIDOMGeoPosition *aSomewhere)
1114 {
1115   if (!WindowOwnerStillExists()) {
1116     Shutdown();
1117     return NS_OK;
1118   }
1119 
1120   if (aSomewhere) {
1121     nsCOMPtr<nsIDOMGeoPositionCoords> coords;
1122     aSomewhere->GetCoords(getter_AddRefs(coords));
1123     if (coords) {
1124       double accuracy = -1;
1125       coords->GetAccuracy(&accuracy);
1126       mozilla::Telemetry::Accumulate(mozilla::Telemetry::GEOLOCATION_ACCURACY_EXPONENTIAL, accuracy);
1127     }
1128   }
1129 
1130   for (uint32_t i = mPendingCallbacks.Length(); i > 0; i--) {
1131     mPendingCallbacks[i-1]->Update(aSomewhere);
1132     RemoveRequest(mPendingCallbacks[i-1]);
1133   }
1134 
1135   // notify everyone that is watching
1136   for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
1137     mWatchingCallbacks[i]->Update(aSomewhere);
1138   }
1139 
1140   return NS_OK;
1141 }
1142 
1143 NS_IMETHODIMP
NotifyError(uint16_t aErrorCode)1144 Geolocation::NotifyError(uint16_t aErrorCode)
1145 {
1146   if (!WindowOwnerStillExists()) {
1147     Shutdown();
1148     return NS_OK;
1149   }
1150 
1151   mozilla::Telemetry::Accumulate(mozilla::Telemetry::GEOLOCATION_ERROR, true);
1152 
1153   for (uint32_t i = mPendingCallbacks.Length(); i > 0; i--) {
1154     mPendingCallbacks[i-1]->NotifyErrorAndShutdown(aErrorCode);
1155     //NotifyErrorAndShutdown() removes the request from the array
1156   }
1157 
1158   // notify everyone that is watching
1159   for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
1160     mWatchingCallbacks[i]->NotifyErrorAndShutdown(aErrorCode);
1161   }
1162 
1163   return NS_OK;
1164 }
1165 
1166 bool
IsAlreadyCleared(nsGeolocationRequest * aRequest)1167 Geolocation::IsAlreadyCleared(nsGeolocationRequest* aRequest)
1168 {
1169   for (uint32_t i = 0, length = mClearedWatchIDs.Length(); i < length; ++i) {
1170     if (mClearedWatchIDs[i] == aRequest->WatchId()) {
1171       return true;
1172     }
1173   }
1174 
1175   return false;
1176 }
1177 
1178 bool
ClearPendingRequest(nsGeolocationRequest * aRequest)1179 Geolocation::ClearPendingRequest(nsGeolocationRequest* aRequest)
1180 {
1181   if (aRequest->IsWatch() && this->IsAlreadyCleared(aRequest)) {
1182     this->NotifyAllowedRequest(aRequest);
1183     this->ClearWatch(aRequest->WatchId());
1184     return true;
1185   }
1186 
1187   return false;
1188 }
1189 
1190 void
GetCurrentPosition(PositionCallback & aCallback,PositionErrorCallback * aErrorCallback,const PositionOptions & aOptions,ErrorResult & aRv)1191 Geolocation::GetCurrentPosition(PositionCallback& aCallback,
1192                                 PositionErrorCallback* aErrorCallback,
1193                                 const PositionOptions& aOptions,
1194                                 ErrorResult& aRv)
1195 {
1196   nsresult rv = GetCurrentPosition(GeoPositionCallback(&aCallback),
1197                                    GeoPositionErrorCallback(aErrorCallback),
1198                                    Move(CreatePositionOptionsCopy(aOptions)));
1199 
1200   if (NS_FAILED(rv)) {
1201     aRv.Throw(rv);
1202   }
1203 
1204   return;
1205 }
1206 
1207 NS_IMETHODIMP
GetCurrentPosition(nsIDOMGeoPositionCallback * aCallback,nsIDOMGeoPositionErrorCallback * aErrorCallback,UniquePtr<PositionOptions> && aOptions)1208 Geolocation::GetCurrentPosition(nsIDOMGeoPositionCallback* aCallback,
1209                                 nsIDOMGeoPositionErrorCallback* aErrorCallback,
1210                                 UniquePtr<PositionOptions>&& aOptions)
1211 {
1212   NS_ENSURE_ARG_POINTER(aCallback);
1213 
1214   return GetCurrentPosition(GeoPositionCallback(aCallback),
1215                             GeoPositionErrorCallback(aErrorCallback),
1216                             Move(aOptions));
1217 }
1218 
1219 nsresult
GetCurrentPosition(GeoPositionCallback callback,GeoPositionErrorCallback errorCallback,UniquePtr<PositionOptions> && options)1220 Geolocation::GetCurrentPosition(GeoPositionCallback callback,
1221                                 GeoPositionErrorCallback errorCallback,
1222                                 UniquePtr<PositionOptions>&& options)
1223 {
1224   if (mPendingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) {
1225     return NS_ERROR_NOT_AVAILABLE;
1226   }
1227 
1228   // After this we hand over ownership of options to our nsGeolocationRequest.
1229 
1230   // Count the number of requests per protocol/scheme.
1231   Telemetry::Accumulate(Telemetry::GEOLOCATION_GETCURRENTPOSITION_SECURE_ORIGIN,
1232                         static_cast<uint8_t>(mProtocolType));
1233 
1234   RefPtr<nsGeolocationRequest> request =
1235     new nsGeolocationRequest(this, Move(callback), Move(errorCallback),
1236                              Move(options), static_cast<uint8_t>(mProtocolType),
1237                              false);
1238 
1239   if (!sGeoEnabled) {
1240     nsCOMPtr<nsIRunnable> ev = new RequestAllowEvent(false, request);
1241     NS_DispatchToMainThread(ev);
1242     return NS_OK;
1243   }
1244 
1245   if (!mOwner && !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
1246     return NS_ERROR_FAILURE;
1247   }
1248 
1249   if (mOwner) {
1250     if (!RegisterRequestWithPrompt(request)) {
1251       return NS_ERROR_NOT_AVAILABLE;
1252     }
1253 
1254     return NS_OK;
1255   }
1256 
1257   if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
1258     return NS_ERROR_FAILURE;
1259   }
1260 
1261   nsCOMPtr<nsIRunnable> ev = new RequestAllowEvent(true, request);
1262   NS_DispatchToMainThread(ev);
1263 
1264   return NS_OK;
1265 }
1266 
1267 int32_t
WatchPosition(PositionCallback & aCallback,PositionErrorCallback * aErrorCallback,const PositionOptions & aOptions,ErrorResult & aRv)1268 Geolocation::WatchPosition(PositionCallback& aCallback,
1269                            PositionErrorCallback* aErrorCallback,
1270                            const PositionOptions& aOptions,
1271                            ErrorResult& aRv)
1272 {
1273   int32_t ret = 0;
1274   nsresult rv = WatchPosition(GeoPositionCallback(&aCallback),
1275                               GeoPositionErrorCallback(aErrorCallback),
1276                               Move(CreatePositionOptionsCopy(aOptions)), &ret);
1277 
1278   if (NS_FAILED(rv)) {
1279     aRv.Throw(rv);
1280   }
1281 
1282   return ret;
1283 }
1284 
1285 NS_IMETHODIMP
WatchPosition(nsIDOMGeoPositionCallback * aCallback,nsIDOMGeoPositionErrorCallback * aErrorCallback,UniquePtr<PositionOptions> && aOptions,int32_t * aRv)1286 Geolocation::WatchPosition(nsIDOMGeoPositionCallback *aCallback,
1287                            nsIDOMGeoPositionErrorCallback *aErrorCallback,
1288                            UniquePtr<PositionOptions>&& aOptions,
1289                            int32_t* aRv)
1290 {
1291   NS_ENSURE_ARG_POINTER(aCallback);
1292 
1293   return WatchPosition(GeoPositionCallback(aCallback),
1294                        GeoPositionErrorCallback(aErrorCallback),
1295                        Move(aOptions), aRv);
1296 }
1297 
1298 nsresult
WatchPosition(GeoPositionCallback aCallback,GeoPositionErrorCallback aErrorCallback,UniquePtr<PositionOptions> && aOptions,int32_t * aRv)1299 Geolocation::WatchPosition(GeoPositionCallback aCallback,
1300                            GeoPositionErrorCallback aErrorCallback,
1301                            UniquePtr<PositionOptions>&& aOptions,
1302                            int32_t* aRv)
1303 {
1304   if (mWatchingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) {
1305     return NS_ERROR_NOT_AVAILABLE;
1306   }
1307 
1308   // Count the number of requests per protocol/scheme.
1309   Telemetry::Accumulate(Telemetry::GEOLOCATION_WATCHPOSITION_SECURE_ORIGIN,
1310                         static_cast<uint8_t>(mProtocolType));
1311 
1312   // The watch ID:
1313   *aRv = mLastWatchId++;
1314 
1315   RefPtr<nsGeolocationRequest> request =
1316     new nsGeolocationRequest(this, Move(aCallback), Move(aErrorCallback),
1317                              Move(aOptions),
1318                              static_cast<uint8_t>(mProtocolType), true, *aRv);
1319 
1320   if (!sGeoEnabled) {
1321     nsCOMPtr<nsIRunnable> ev = new RequestAllowEvent(false, request);
1322     NS_DispatchToMainThread(ev);
1323     return NS_OK;
1324   }
1325 
1326   if (!mOwner && !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
1327     return NS_ERROR_FAILURE;
1328   }
1329 
1330   if (mOwner) {
1331     if (!RegisterRequestWithPrompt(request))
1332       return NS_ERROR_NOT_AVAILABLE;
1333 
1334     return NS_OK;
1335   }
1336 
1337   if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
1338     return NS_ERROR_FAILURE;
1339   }
1340 
1341   request->Allow(JS::UndefinedHandleValue);
1342 
1343   return NS_OK;
1344 }
1345 
1346 NS_IMETHODIMP
ClearWatch(int32_t aWatchId)1347 Geolocation::ClearWatch(int32_t aWatchId)
1348 {
1349   if (aWatchId < 0) {
1350     return NS_OK;
1351   }
1352 
1353   if (!mClearedWatchIDs.Contains(aWatchId)) {
1354     mClearedWatchIDs.AppendElement(aWatchId);
1355   }
1356 
1357   for (uint32_t i = 0, length = mWatchingCallbacks.Length(); i < length; ++i) {
1358     if (mWatchingCallbacks[i]->WatchId() == aWatchId) {
1359       mWatchingCallbacks[i]->Shutdown();
1360       RemoveRequest(mWatchingCallbacks[i]);
1361       mClearedWatchIDs.RemoveElement(aWatchId);
1362       break;
1363     }
1364   }
1365 
1366   // make sure we also search through the pending requests lists for
1367   // watches to clear...
1368   for (uint32_t i = 0, length = mPendingRequests.Length(); i < length; ++i) {
1369     if (mPendingRequests[i]->IsWatch() &&
1370         (mPendingRequests[i]->WatchId() == aWatchId)) {
1371       mPendingRequests[i]->Shutdown();
1372       mPendingRequests.RemoveElementAt(i);
1373       break;
1374     }
1375   }
1376 
1377   return NS_OK;
1378 }
1379 
1380 bool
WindowOwnerStillExists()1381 Geolocation::WindowOwnerStillExists()
1382 {
1383   // an owner was never set when Geolocation
1384   // was created, which means that this object
1385   // is being used without a window.
1386   if (mOwner == nullptr) {
1387     return true;
1388   }
1389 
1390   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mOwner);
1391 
1392   if (window) {
1393     nsPIDOMWindowOuter* outer = window->GetOuterWindow();
1394     if (!outer || outer->GetCurrentInnerWindow() != window ||
1395         outer->Closed()) {
1396       return false;
1397     }
1398   }
1399 
1400   return true;
1401 }
1402 
1403 void
NotifyAllowedRequest(nsGeolocationRequest * aRequest)1404 Geolocation::NotifyAllowedRequest(nsGeolocationRequest* aRequest)
1405 {
1406   if (aRequest->IsWatch()) {
1407     mWatchingCallbacks.AppendElement(aRequest);
1408   } else {
1409     mPendingCallbacks.AppendElement(aRequest);
1410   }
1411 }
1412 
1413 bool
RegisterRequestWithPrompt(nsGeolocationRequest * request)1414 Geolocation::RegisterRequestWithPrompt(nsGeolocationRequest* request)
1415 {
1416   if (Preferences::GetBool("geo.prompt.testing", false)) {
1417     bool allow = Preferences::GetBool("geo.prompt.testing.allow", false);
1418     nsCOMPtr<nsIRunnable> ev = new RequestAllowEvent(allow, request);
1419     NS_DispatchToMainThread(ev);
1420     return true;
1421   }
1422 
1423   nsCOMPtr<nsIRunnable> ev  = new RequestPromptEvent(request, mOwner);
1424   NS_DispatchToMainThread(ev);
1425   return true;
1426 }
1427 
1428 JSObject*
WrapObject(JSContext * aCtx,JS::Handle<JSObject * > aGivenProto)1429 Geolocation::WrapObject(JSContext *aCtx, JS::Handle<JSObject*> aGivenProto)
1430 {
1431   return GeolocationBinding::Wrap(aCtx, this, aGivenProto);
1432 }
1433