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