summaryrefslogtreecommitdiffstats
path: root/dom/geolocation/nsGeolocation.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/geolocation/nsGeolocation.cpp')
-rw-r--r--dom/geolocation/nsGeolocation.cpp1432
1 files changed, 1432 insertions, 0 deletions
diff --git a/dom/geolocation/nsGeolocation.cpp b/dom/geolocation/nsGeolocation.cpp
new file mode 100644
index 000000000..9fcdb350a
--- /dev/null
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -0,0 +1,1432 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsXULAppAPI.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsGeolocation.h"
+#include "nsDOMClassInfoID.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsContentPermissionHelper.h"
+#include "nsIDocument.h"
+#include "nsIObserverService.h"
+#include "nsPIDOMWindow.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+
+class nsIPrincipal;
+
+#ifdef MOZ_WIDGET_ANDROID
+#include "AndroidLocationProvider.h"
+#endif
+
+#ifdef MOZ_WIDGET_GONK
+#include "GonkGPSGeolocationProvider.h"
+#endif
+
+#ifdef MOZ_GPSD
+#include "GpsdLocationProvider.h"
+#endif
+
+#ifdef MOZ_WIDGET_COCOA
+#include "CoreLocationLocationProvider.h"
+#endif
+
+#ifdef XP_WIN
+#include "WindowsLocationProvider.h"
+#include "mozilla/WindowsVersion.h"
+#endif
+
+// Some limit to the number of get or watch geolocation requests
+// that a window can make.
+#define MAX_GEO_REQUESTS_PER_WINDOW 1500
+
+using mozilla::Unused; // <snicker>
+using namespace mozilla;
+using namespace mozilla::dom;
+
+class nsGeolocationRequest final
+ : public nsIContentPermissionRequest
+ , public nsIGeolocationUpdate
+ , public SupportsWeakPtr<nsGeolocationRequest>
+{
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSICONTENTPERMISSIONREQUEST
+ NS_DECL_NSIGEOLOCATIONUPDATE
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsGeolocationRequest, nsIContentPermissionRequest)
+
+ nsGeolocationRequest(Geolocation* aLocator,
+ GeoPositionCallback aCallback,
+ GeoPositionErrorCallback aErrorCallback,
+ UniquePtr<PositionOptions>&& aOptions,
+ uint8_t aProtocolType,
+ bool aWatchPositionRequest = false,
+ int32_t aWatchId = 0);
+
+ MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsGeolocationRequest)
+
+ void Shutdown();
+
+ void SendLocation(nsIDOMGeoPosition* aLocation);
+ bool WantsHighAccuracy() {return !mShutdown && mOptions && mOptions->mEnableHighAccuracy;}
+ void SetTimeoutTimer();
+ void StopTimeoutTimer();
+ void NotifyErrorAndShutdown(uint16_t);
+ nsIPrincipal* GetPrincipal();
+
+ bool IsWatch() { return mIsWatchPositionRequest; }
+ int32_t WatchId() { return mWatchId; }
+ private:
+ virtual ~nsGeolocationRequest();
+
+ class TimerCallbackHolder final : public nsITimerCallback
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ explicit TimerCallbackHolder(nsGeolocationRequest* aRequest)
+ : mRequest(aRequest)
+ {}
+
+ private:
+ ~TimerCallbackHolder() {}
+ WeakPtr<nsGeolocationRequest> mRequest;
+ };
+
+ void Notify();
+
+ bool mIsWatchPositionRequest;
+
+ nsCOMPtr<nsITimer> mTimeoutTimer;
+ GeoPositionCallback mCallback;
+ GeoPositionErrorCallback mErrorCallback;
+ UniquePtr<PositionOptions> mOptions;
+
+ RefPtr<Geolocation> mLocator;
+
+ int32_t mWatchId;
+ bool mShutdown;
+ nsCOMPtr<nsIContentPermissionRequester> mRequester;
+ uint8_t mProtocolType;
+};
+
+static UniquePtr<PositionOptions>
+CreatePositionOptionsCopy(const PositionOptions& aOptions)
+{
+ UniquePtr<PositionOptions> geoOptions = MakeUnique<PositionOptions>();
+
+ geoOptions->mEnableHighAccuracy = aOptions.mEnableHighAccuracy;
+ geoOptions->mMaximumAge = aOptions.mMaximumAge;
+ geoOptions->mTimeout = aOptions.mTimeout;
+
+ return geoOptions;
+}
+
+class RequestPromptEvent : public Runnable
+{
+public:
+ RequestPromptEvent(nsGeolocationRequest* aRequest, nsWeakPtr aWindow)
+ : mRequest(aRequest)
+ , mWindow(aWindow)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mWindow);
+ nsContentPermissionUtils::AskPermission(mRequest, window);
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsGeolocationRequest> mRequest;
+ nsWeakPtr mWindow;
+};
+
+class RequestAllowEvent : public Runnable
+{
+public:
+ RequestAllowEvent(int allow, nsGeolocationRequest* request)
+ : mAllow(allow),
+ mRequest(request)
+ {
+ }
+
+ NS_IMETHOD Run() override {
+ if (mAllow) {
+ mRequest->Allow(JS::UndefinedHandleValue);
+ } else {
+ mRequest->Cancel();
+ }
+ return NS_OK;
+ }
+
+private:
+ bool mAllow;
+ RefPtr<nsGeolocationRequest> mRequest;
+};
+
+class RequestSendLocationEvent : public Runnable
+{
+public:
+ RequestSendLocationEvent(nsIDOMGeoPosition* aPosition,
+ nsGeolocationRequest* aRequest)
+ : mPosition(aPosition),
+ mRequest(aRequest)
+ {
+ }
+
+ NS_IMETHOD Run() override {
+ mRequest->SendLocation(mPosition);
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIDOMGeoPosition> mPosition;
+ RefPtr<nsGeolocationRequest> mRequest;
+ RefPtr<Geolocation> mLocator;
+};
+
+////////////////////////////////////////////////////
+// PositionError
+////////////////////////////////////////////////////
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PositionError)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMGeoPositionError)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMGeoPositionError)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PositionError, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PositionError)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PositionError)
+
+PositionError::PositionError(Geolocation* aParent, int16_t aCode)
+ : mCode(aCode)
+ , mParent(aParent)
+{
+}
+
+PositionError::~PositionError(){}
+
+
+NS_IMETHODIMP
+PositionError::GetCode(int16_t *aCode)
+{
+ NS_ENSURE_ARG_POINTER(aCode);
+ *aCode = Code();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PositionError::GetMessage(nsAString& aMessage)
+{
+ switch (mCode)
+ {
+ case nsIDOMGeoPositionError::PERMISSION_DENIED:
+ aMessage = NS_LITERAL_STRING("User denied geolocation prompt");
+ break;
+ case nsIDOMGeoPositionError::POSITION_UNAVAILABLE:
+ aMessage = NS_LITERAL_STRING("Unknown error acquiring position");
+ break;
+ case nsIDOMGeoPositionError::TIMEOUT:
+ aMessage = NS_LITERAL_STRING("Position acquisition timed out");
+ break;
+ default:
+ break;
+ }
+ return NS_OK;
+}
+
+Geolocation*
+PositionError::GetParentObject() const
+{
+ return mParent;
+}
+
+JSObject*
+PositionError::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return PositionErrorBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+PositionError::NotifyCallback(const GeoPositionErrorCallback& aCallback)
+{
+ nsAutoMicroTask mt;
+ if (aCallback.HasWebIDLCallback()) {
+ PositionErrorCallback* callback = aCallback.GetWebIDLCallback();
+
+ if (callback) {
+ callback->Call(*this);
+ }
+ } else {
+ nsIDOMGeoPositionErrorCallback* callback = aCallback.GetXPCOMCallback();
+ if (callback) {
+ callback->HandleEvent(this);
+ }
+ }
+}
+////////////////////////////////////////////////////
+// nsGeolocationRequest
+////////////////////////////////////////////////////
+
+nsGeolocationRequest::nsGeolocationRequest(Geolocation* aLocator,
+ GeoPositionCallback aCallback,
+ GeoPositionErrorCallback aErrorCallback,
+ UniquePtr<PositionOptions>&& aOptions,
+ uint8_t aProtocolType,
+ bool aWatchPositionRequest,
+ int32_t aWatchId)
+ : mIsWatchPositionRequest(aWatchPositionRequest),
+ mCallback(Move(aCallback)),
+ mErrorCallback(Move(aErrorCallback)),
+ mOptions(Move(aOptions)),
+ mLocator(aLocator),
+ mWatchId(aWatchId),
+ mShutdown(false),
+ mProtocolType(aProtocolType)
+{
+ if (nsCOMPtr<nsPIDOMWindowInner> win =
+ do_QueryReferent(mLocator->GetOwner())) {
+ mRequester = new nsContentPermissionRequester(win);
+ }
+}
+
+nsGeolocationRequest::~nsGeolocationRequest()
+{
+ StopTimeoutTimer();
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGeolocationRequest)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGeolocationRequest)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGeolocationRequest)
+NS_IMPL_CYCLE_COLLECTION(nsGeolocationRequest, mCallback, mErrorCallback, mLocator)
+
+void
+nsGeolocationRequest::Notify()
+{
+ SetTimeoutTimer();
+ NotifyErrorAndShutdown(nsIDOMGeoPositionError::TIMEOUT);
+}
+
+void
+nsGeolocationRequest::NotifyErrorAndShutdown(uint16_t aErrorCode)
+{
+ MOZ_ASSERT(!mShutdown, "timeout after shutdown");
+ if (!mIsWatchPositionRequest) {
+ Shutdown();
+ mLocator->RemoveRequest(this);
+ }
+
+ NotifyError(aErrorCode);
+}
+
+NS_IMETHODIMP
+nsGeolocationRequest::GetPrincipal(nsIPrincipal * *aRequestingPrincipal)
+{
+ NS_ENSURE_ARG_POINTER(aRequestingPrincipal);
+
+ nsCOMPtr<nsIPrincipal> principal = mLocator->GetPrincipal();
+ principal.forget(aRequestingPrincipal);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGeolocationRequest::GetTypes(nsIArray** aTypes)
+{
+ nsTArray<nsString> emptyOptions;
+ return nsContentPermissionUtils::CreatePermissionArray(NS_LITERAL_CSTRING("geolocation"),
+ NS_LITERAL_CSTRING("unused"),
+ emptyOptions,
+ aTypes);
+}
+
+NS_IMETHODIMP
+nsGeolocationRequest::GetWindow(mozIDOMWindow** aRequestingWindow)
+{
+ NS_ENSURE_ARG_POINTER(aRequestingWindow);
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mLocator->GetOwner());
+ window.forget(aRequestingWindow);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGeolocationRequest::GetElement(nsIDOMElement * *aRequestingElement)
+{
+ NS_ENSURE_ARG_POINTER(aRequestingElement);
+ *aRequestingElement = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGeolocationRequest::Cancel()
+{
+ if (mRequester) {
+ // Record the number of denied requests for regular web content.
+ // This method is only called when the user explicitly denies the request,
+ // and is not called when the page is simply unloaded, or similar.
+ Telemetry::Accumulate(Telemetry::GEOLOCATION_REQUEST_GRANTED, mProtocolType);
+ }
+
+ if (mLocator->ClearPendingRequest(this)) {
+ return NS_OK;
+ }
+
+ NotifyError(nsIDOMGeoPositionError::PERMISSION_DENIED);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGeolocationRequest::Allow(JS::HandleValue aChoices)
+{
+ MOZ_ASSERT(aChoices.isUndefined());
+
+ if (mRequester) {
+ // Record the number of granted requests for regular web content.
+ Telemetry::Accumulate(Telemetry::GEOLOCATION_REQUEST_GRANTED, mProtocolType + 10);
+
+ // Record whether a location callback is fulfilled while the owner window
+ // is not visible.
+ bool isVisible = false;
+ nsCOMPtr<nsPIDOMWindowInner> window = mLocator->GetParentObject();
+
+ if (window) {
+ nsCOMPtr<nsIDocument> doc = window->GetDoc();
+ isVisible = doc && !doc->Hidden();
+ }
+
+ if (IsWatch()) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::GEOLOCATION_WATCHPOSITION_VISIBLE, isVisible);
+ } else {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::GEOLOCATION_GETCURRENTPOSITION_VISIBLE, isVisible);
+ }
+ }
+
+ if (mLocator->ClearPendingRequest(this)) {
+ return NS_OK;
+ }
+
+ RefPtr<nsGeolocationService> gs = nsGeolocationService::GetGeolocationService();
+
+ bool canUseCache = false;
+ CachedPositionAndAccuracy lastPosition = gs->GetCachedPosition();
+ if (lastPosition.position) {
+ DOMTimeStamp cachedPositionTime_ms;
+ lastPosition.position->GetTimestamp(&cachedPositionTime_ms);
+ // check to see if we can use a cached value
+ // if the user has specified a maximumAge, return a cached value.
+ if (mOptions && mOptions->mMaximumAge > 0) {
+ uint32_t maximumAge_ms = mOptions->mMaximumAge;
+ bool isCachedWithinRequestedAccuracy = WantsHighAccuracy() <= lastPosition.isHighAccuracy;
+ bool isCachedWithinRequestedTime =
+ DOMTimeStamp(PR_Now() / PR_USEC_PER_MSEC - maximumAge_ms) <= cachedPositionTime_ms;
+ canUseCache = isCachedWithinRequestedAccuracy && isCachedWithinRequestedTime;
+ }
+ }
+
+ gs->UpdateAccuracy(WantsHighAccuracy());
+ if (canUseCache) {
+ // okay, we can return a cached position
+ // getCurrentPosition requests serviced by the cache
+ // will now be owned by the RequestSendLocationEvent
+ Update(lastPosition.position);
+
+ // After Update is called, getCurrentPosition finishes it's job.
+ if (!mIsWatchPositionRequest) {
+ return NS_OK;
+ }
+
+ } else {
+ // if it is not a watch request and timeout is 0,
+ // invoke the errorCallback (if present) with TIMEOUT code
+ if (mOptions && mOptions->mTimeout == 0 && !mIsWatchPositionRequest) {
+ NotifyError(nsIDOMGeoPositionError::TIMEOUT);
+ return NS_OK;
+ }
+
+ }
+
+ // Kick off the geo device, if it isn't already running
+ nsresult rv = gs->StartDevice(GetPrincipal());
+
+ if (NS_FAILED(rv)) {
+ // Location provider error
+ NotifyError(nsIDOMGeoPositionError::POSITION_UNAVAILABLE);
+ return NS_OK;
+ }
+
+ if (mIsWatchPositionRequest || !canUseCache) {
+ // let the locator know we're pending
+ // we will now be owned by the locator
+ mLocator->NotifyAllowedRequest(this);
+ }
+
+ SetTimeoutTimer();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGeolocationRequest::GetRequester(nsIContentPermissionRequester** aRequester)
+{
+ NS_ENSURE_ARG_POINTER(aRequester);
+
+ nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
+ requester.forget(aRequester);
+
+ return NS_OK;
+}
+
+void
+nsGeolocationRequest::SetTimeoutTimer()
+{
+ MOZ_ASSERT(!mShutdown, "set timeout after shutdown");
+
+ StopTimeoutTimer();
+
+ if (mOptions && mOptions->mTimeout != 0 && mOptions->mTimeout != 0x7fffffff) {
+ mTimeoutTimer = do_CreateInstance("@mozilla.org/timer;1");
+ RefPtr<TimerCallbackHolder> holder = new TimerCallbackHolder(this);
+ mTimeoutTimer->InitWithCallback(holder, mOptions->mTimeout, nsITimer::TYPE_ONE_SHOT);
+ }
+}
+
+void
+nsGeolocationRequest::StopTimeoutTimer()
+{
+ if (mTimeoutTimer) {
+ mTimeoutTimer->Cancel();
+ mTimeoutTimer = nullptr;
+ }
+}
+
+void
+nsGeolocationRequest::SendLocation(nsIDOMGeoPosition* aPosition)
+{
+ if (mShutdown) {
+ // Ignore SendLocationEvents issued before we were cleared.
+ return;
+ }
+
+ if (mOptions && mOptions->mMaximumAge > 0) {
+ DOMTimeStamp positionTime_ms;
+ aPosition->GetTimestamp(&positionTime_ms);
+ const uint32_t maximumAge_ms = mOptions->mMaximumAge;
+ const bool isTooOld =
+ DOMTimeStamp(PR_Now() / PR_USEC_PER_MSEC - maximumAge_ms) > positionTime_ms;
+ if (isTooOld) {
+ return;
+ }
+ }
+
+ RefPtr<Position> wrapped;
+
+ if (aPosition) {
+ nsCOMPtr<nsIDOMGeoPositionCoords> coords;
+ aPosition->GetCoords(getter_AddRefs(coords));
+ if (coords) {
+ wrapped = new Position(ToSupports(mLocator), aPosition);
+ }
+ }
+
+ if (!wrapped) {
+ NotifyError(nsIDOMGeoPositionError::POSITION_UNAVAILABLE);
+ return;
+ }
+
+ if (!mIsWatchPositionRequest) {
+ // Cancel timer and position updates in case the position
+ // callback spins the event loop
+ Shutdown();
+ }
+
+ nsAutoMicroTask mt;
+ if (mCallback.HasWebIDLCallback()) {
+ PositionCallback* callback = mCallback.GetWebIDLCallback();
+
+ MOZ_ASSERT(callback);
+ callback->Call(*wrapped);
+ } else {
+ nsIDOMGeoPositionCallback* callback = mCallback.GetXPCOMCallback();
+ MOZ_ASSERT(callback);
+ callback->HandleEvent(aPosition);
+ }
+
+ if (mIsWatchPositionRequest && !mShutdown) {
+ SetTimeoutTimer();
+ }
+ MOZ_ASSERT(mShutdown || mIsWatchPositionRequest,
+ "non-shutdown getCurrentPosition request after callback!");
+}
+
+nsIPrincipal*
+nsGeolocationRequest::GetPrincipal()
+{
+ if (!mLocator) {
+ return nullptr;
+ }
+ return mLocator->GetPrincipal();
+}
+
+NS_IMETHODIMP
+nsGeolocationRequest::Update(nsIDOMGeoPosition* aPosition)
+{
+ nsCOMPtr<nsIRunnable> ev = new RequestSendLocationEvent(aPosition, this);
+ NS_DispatchToMainThread(ev);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGeolocationRequest::NotifyError(uint16_t aErrorCode)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<PositionError> positionError = new PositionError(mLocator, aErrorCode);
+ positionError->NotifyCallback(mErrorCallback);
+ return NS_OK;
+}
+
+void
+nsGeolocationRequest::Shutdown()
+{
+ MOZ_ASSERT(!mShutdown, "request shutdown twice");
+ mShutdown = true;
+
+ StopTimeoutTimer();
+
+ // If there are no other high accuracy requests, the geolocation service will
+ // notify the provider to switch to the default accuracy.
+ if (mOptions && mOptions->mEnableHighAccuracy) {
+ RefPtr<nsGeolocationService> gs = nsGeolocationService::GetGeolocationService();
+ if (gs) {
+ gs->UpdateAccuracy();
+ }
+ }
+}
+
+
+////////////////////////////////////////////////////
+// nsGeolocationRequest::TimerCallbackHolder
+////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder, nsISupports, nsITimerCallback)
+
+NS_IMETHODIMP
+nsGeolocationRequest::TimerCallbackHolder::Notify(nsITimer*)
+{
+ if (mRequest && mRequest->mLocator) {
+ RefPtr<nsGeolocationRequest> request(mRequest);
+ request->Notify();
+ }
+
+ return NS_OK;
+}
+
+
+////////////////////////////////////////////////////
+// nsGeolocationService
+////////////////////////////////////////////////////
+NS_INTERFACE_MAP_BEGIN(nsGeolocationService)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIGeolocationUpdate)
+ NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(nsGeolocationService)
+NS_IMPL_RELEASE(nsGeolocationService)
+
+
+static bool sGeoEnabled = true;
+static int32_t sProviderTimeout = 6000; // Time, in milliseconds, to wait for the location provider to spin up.
+
+nsresult nsGeolocationService::Init()
+{
+ Preferences::AddIntVarCache(&sProviderTimeout, "geo.timeout", sProviderTimeout);
+ Preferences::AddBoolVarCache(&sGeoEnabled, "geo.enabled", sGeoEnabled);
+
+ if (!sGeoEnabled) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (XRE_IsContentProcess()) {
+ return NS_OK;
+ }
+
+ // geolocation service can be enabled -> now register observer
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (!obs) {
+ return NS_ERROR_FAILURE;
+ }
+
+ obs->AddObserver(this, "xpcom-shutdown", false);
+
+#ifdef MOZ_WIDGET_ANDROID
+ mProvider = new AndroidLocationProvider();
+#endif
+
+#ifdef MOZ_WIDGET_GONK
+ // GonkGPSGeolocationProvider can be started at boot up time for initialization reasons.
+ // do_getService gets hold of the already initialized component and starts
+ // processing location requests immediately.
+ // do_Createinstance will create multiple instances of the provider which is not right.
+ // bug 993041
+ mProvider = do_GetService(GONK_GPS_GEOLOCATION_PROVIDER_CONTRACTID);
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+#ifdef MOZ_GPSD
+ if (Preferences::GetBool("geo.provider.use_gpsd", false)) {
+ mProvider = new GpsdLocationProvider();
+ }
+#endif
+#endif
+
+#ifdef MOZ_WIDGET_COCOA
+ if (Preferences::GetBool("geo.provider.use_corelocation", true)) {
+ mProvider = new CoreLocationLocationProvider();
+ }
+#endif
+
+#ifdef XP_WIN
+ if (Preferences::GetBool("geo.provider.ms-windows-location", false) &&
+ IsWin8OrLater()) {
+ mProvider = new WindowsLocationProvider();
+ }
+#endif
+
+ if (Preferences::GetBool("geo.provider.use_mls", false)) {
+ mProvider = do_CreateInstance("@mozilla.org/geolocation/mls-provider;1");
+ }
+
+ // Override platform-specific providers with the default (network)
+ // provider while testing. Our tests are currently not meant to exercise
+ // the provider, and some tests rely on the network provider being used.
+ // "geo.provider.testing" is always set for all plain and browser chrome
+ // mochitests, and also for xpcshell tests.
+ if (!mProvider || Preferences::GetBool("geo.provider.testing", false)) {
+ nsCOMPtr<nsIGeolocationProvider> override =
+ do_GetService(NS_GEOLOCATION_PROVIDER_CONTRACTID);
+
+ if (override) {
+ mProvider = override;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsGeolocationService::~nsGeolocationService()
+{
+}
+
+NS_IMETHODIMP
+nsGeolocationService::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ if (!strcmp("xpcom-shutdown", aTopic)) {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "xpcom-shutdown");
+ }
+
+ for (uint32_t i = 0; i< mGeolocators.Length(); i++) {
+ mGeolocators[i]->Shutdown();
+ }
+ StopDevice();
+
+ return NS_OK;
+ }
+
+ if (!strcmp("timer-callback", aTopic)) {
+ // decide if we can close down the service.
+ for (uint32_t i = 0; i< mGeolocators.Length(); i++)
+ if (mGeolocators[i]->HasActiveCallbacks()) {
+ SetDisconnectTimer();
+ return NS_OK;
+ }
+
+ // okay to close up.
+ StopDevice();
+ Update(nullptr);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsGeolocationService::Update(nsIDOMGeoPosition *aSomewhere)
+{
+ if (aSomewhere) {
+ SetCachedPosition(aSomewhere);
+ }
+
+ for (uint32_t i = 0; i< mGeolocators.Length(); i++) {
+ mGeolocators[i]->Update(aSomewhere);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGeolocationService::NotifyError(uint16_t aErrorCode)
+{
+ for (uint32_t i = 0; i < mGeolocators.Length(); i++) {
+ mGeolocators[i]->NotifyError(aErrorCode);
+ }
+ return NS_OK;
+}
+
+void
+nsGeolocationService::SetCachedPosition(nsIDOMGeoPosition* aPosition)
+{
+ mLastPosition.position = aPosition;
+ mLastPosition.isHighAccuracy = mHigherAccuracy;
+}
+
+CachedPositionAndAccuracy
+nsGeolocationService::GetCachedPosition()
+{
+ return mLastPosition;
+}
+
+nsresult
+nsGeolocationService::StartDevice(nsIPrincipal *aPrincipal)
+{
+ if (!sGeoEnabled) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // We do not want to keep the geolocation devices online
+ // indefinitely.
+ // Close them down after a reasonable period of inactivivity.
+ SetDisconnectTimer();
+
+ if (XRE_IsContentProcess()) {
+ ContentChild* cpc = ContentChild::GetSingleton();
+ cpc->SendAddGeolocationListener(IPC::Principal(aPrincipal),
+ HighAccuracyRequested());
+ return NS_OK;
+ }
+
+ // Start them up!
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (!obs) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mProvider) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+
+ if (NS_FAILED(rv = mProvider->Startup()) ||
+ NS_FAILED(rv = mProvider->Watch(this))) {
+
+ NotifyError(nsIDOMGeoPositionError::POSITION_UNAVAILABLE);
+ return rv;
+ }
+
+ obs->NotifyObservers(mProvider,
+ "geolocation-device-events",
+ u"starting");
+
+ return NS_OK;
+}
+
+void
+nsGeolocationService::SetDisconnectTimer()
+{
+ if (!mDisconnectTimer) {
+ mDisconnectTimer = do_CreateInstance("@mozilla.org/timer;1");
+ } else {
+ mDisconnectTimer->Cancel();
+ }
+
+ mDisconnectTimer->Init(this,
+ sProviderTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+}
+
+bool
+nsGeolocationService::HighAccuracyRequested()
+{
+ for (uint32_t i = 0; i < mGeolocators.Length(); i++) {
+ if (mGeolocators[i]->HighAccuracyRequested()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+nsGeolocationService::UpdateAccuracy(bool aForceHigh)
+{
+ bool highRequired = aForceHigh || HighAccuracyRequested();
+
+ if (XRE_IsContentProcess()) {
+ ContentChild* cpc = ContentChild::GetSingleton();
+ if (cpc->IsAlive()) {
+ cpc->SendSetGeolocationHigherAccuracy(highRequired);
+ }
+
+ return;
+ }
+
+ mProvider->SetHighAccuracy(!mHigherAccuracy && highRequired);
+ mHigherAccuracy = highRequired;
+}
+
+void
+nsGeolocationService::StopDevice()
+{
+ if (mDisconnectTimer) {
+ mDisconnectTimer->Cancel();
+ mDisconnectTimer = nullptr;
+ }
+
+ if (XRE_IsContentProcess()) {
+ ContentChild* cpc = ContentChild::GetSingleton();
+ cpc->SendRemoveGeolocationListener();
+
+ return; // bail early
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (!obs) {
+ return;
+ }
+
+ if (!mProvider) {
+ return;
+ }
+
+ mHigherAccuracy = false;
+
+ mProvider->Shutdown();
+ obs->NotifyObservers(mProvider,
+ "geolocation-device-events",
+ u"shutdown");
+}
+
+StaticRefPtr<nsGeolocationService> nsGeolocationService::sService;
+
+already_AddRefed<nsGeolocationService>
+nsGeolocationService::GetGeolocationService()
+{
+ RefPtr<nsGeolocationService> result;
+ if (nsGeolocationService::sService) {
+ result = nsGeolocationService::sService;
+
+ return result.forget();
+ }
+
+ result = new nsGeolocationService();
+ if (NS_FAILED(result->Init())) {
+ return nullptr;
+ }
+
+ ClearOnShutdown(&nsGeolocationService::sService);
+ nsGeolocationService::sService = result;
+ return result.forget();
+}
+
+void
+nsGeolocationService::AddLocator(Geolocation* aLocator)
+{
+ mGeolocators.AppendElement(aLocator);
+}
+
+void
+nsGeolocationService::RemoveLocator(Geolocation* aLocator)
+{
+ mGeolocators.RemoveElement(aLocator);
+}
+
+////////////////////////////////////////////////////
+// Geolocation
+////////////////////////////////////////////////////
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Geolocation)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMGeoGeolocation)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMGeoGeolocation)
+ NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Geolocation)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Geolocation)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Geolocation,
+ mPendingCallbacks,
+ mWatchingCallbacks,
+ mPendingRequests)
+
+Geolocation::Geolocation()
+: mProtocolType(ProtocolType::OTHER)
+, mLastWatchId(0)
+{
+}
+
+Geolocation::~Geolocation()
+{
+ if (mService) {
+ Shutdown();
+ }
+}
+
+nsresult
+Geolocation::Init(nsPIDOMWindowInner* aContentDom)
+{
+ // Remember the window
+ if (aContentDom) {
+ mOwner = do_GetWeakReference(aContentDom);
+ if (!mOwner) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Grab the principal of the document
+ nsCOMPtr<nsIDocument> doc = aContentDom->GetDoc();
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mPrincipal = doc->NodePrincipal();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = mPrincipal->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (uri) {
+ bool isHttp;
+ rv = uri->SchemeIs("http", &isHttp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isHttps;
+ rv = uri->SchemeIs("https", &isHttps);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Store the protocol to send via telemetry later.
+ if (isHttp) {
+ mProtocolType = ProtocolType::HTTP;
+ } else if (isHttps) {
+ mProtocolType = ProtocolType::HTTPS;
+ }
+ }
+ }
+
+ // If no aContentDom was passed into us, we are being used
+ // by chrome/c++ and have no mOwner, no mPrincipal, and no need
+ // to prompt.
+ mService = nsGeolocationService::GetGeolocationService();
+ if (mService) {
+ mService->AddLocator(this);
+ }
+
+ return NS_OK;
+}
+
+void
+Geolocation::Shutdown()
+{
+ // Release all callbacks
+ mPendingCallbacks.Clear();
+ mWatchingCallbacks.Clear();
+
+ if (mService) {
+ mService->RemoveLocator(this);
+ mService->UpdateAccuracy();
+ }
+
+ mService = nullptr;
+ mPrincipal = nullptr;
+}
+
+nsPIDOMWindowInner*
+Geolocation::GetParentObject() const {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mOwner);
+ return window.get();
+}
+
+bool
+Geolocation::HasActiveCallbacks()
+{
+ return mPendingCallbacks.Length() || mWatchingCallbacks.Length();
+}
+
+bool
+Geolocation::HighAccuracyRequested()
+{
+ for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
+ if (mWatchingCallbacks[i]->WantsHighAccuracy()) {
+ return true;
+ }
+ }
+
+ for (uint32_t i = 0; i < mPendingCallbacks.Length(); i++) {
+ if (mPendingCallbacks[i]->WantsHighAccuracy()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+Geolocation::RemoveRequest(nsGeolocationRequest* aRequest)
+{
+ bool requestWasKnown =
+ (mPendingCallbacks.RemoveElement(aRequest) !=
+ mWatchingCallbacks.RemoveElement(aRequest));
+
+ Unused << requestWasKnown;
+}
+
+NS_IMETHODIMP
+Geolocation::Update(nsIDOMGeoPosition *aSomewhere)
+{
+ if (!WindowOwnerStillExists()) {
+ Shutdown();
+ return NS_OK;
+ }
+
+ if (aSomewhere) {
+ nsCOMPtr<nsIDOMGeoPositionCoords> coords;
+ aSomewhere->GetCoords(getter_AddRefs(coords));
+ if (coords) {
+ double accuracy = -1;
+ coords->GetAccuracy(&accuracy);
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::GEOLOCATION_ACCURACY_EXPONENTIAL, accuracy);
+ }
+ }
+
+ for (uint32_t i = mPendingCallbacks.Length(); i > 0; i--) {
+ mPendingCallbacks[i-1]->Update(aSomewhere);
+ RemoveRequest(mPendingCallbacks[i-1]);
+ }
+
+ // notify everyone that is watching
+ for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
+ mWatchingCallbacks[i]->Update(aSomewhere);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Geolocation::NotifyError(uint16_t aErrorCode)
+{
+ if (!WindowOwnerStillExists()) {
+ Shutdown();
+ return NS_OK;
+ }
+
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::GEOLOCATION_ERROR, true);
+
+ for (uint32_t i = mPendingCallbacks.Length(); i > 0; i--) {
+ mPendingCallbacks[i-1]->NotifyErrorAndShutdown(aErrorCode);
+ //NotifyErrorAndShutdown() removes the request from the array
+ }
+
+ // notify everyone that is watching
+ for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
+ mWatchingCallbacks[i]->NotifyErrorAndShutdown(aErrorCode);
+ }
+
+ return NS_OK;
+}
+
+bool
+Geolocation::IsAlreadyCleared(nsGeolocationRequest* aRequest)
+{
+ for (uint32_t i = 0, length = mClearedWatchIDs.Length(); i < length; ++i) {
+ if (mClearedWatchIDs[i] == aRequest->WatchId()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+Geolocation::ClearPendingRequest(nsGeolocationRequest* aRequest)
+{
+ if (aRequest->IsWatch() && this->IsAlreadyCleared(aRequest)) {
+ this->NotifyAllowedRequest(aRequest);
+ this->ClearWatch(aRequest->WatchId());
+ return true;
+ }
+
+ return false;
+}
+
+void
+Geolocation::GetCurrentPosition(PositionCallback& aCallback,
+ PositionErrorCallback* aErrorCallback,
+ const PositionOptions& aOptions,
+ ErrorResult& aRv)
+{
+ nsresult rv = GetCurrentPosition(GeoPositionCallback(&aCallback),
+ GeoPositionErrorCallback(aErrorCallback),
+ Move(CreatePositionOptionsCopy(aOptions)));
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+
+ return;
+}
+
+NS_IMETHODIMP
+Geolocation::GetCurrentPosition(nsIDOMGeoPositionCallback* aCallback,
+ nsIDOMGeoPositionErrorCallback* aErrorCallback,
+ UniquePtr<PositionOptions>&& aOptions)
+{
+ NS_ENSURE_ARG_POINTER(aCallback);
+
+ return GetCurrentPosition(GeoPositionCallback(aCallback),
+ GeoPositionErrorCallback(aErrorCallback),
+ Move(aOptions));
+}
+
+nsresult
+Geolocation::GetCurrentPosition(GeoPositionCallback callback,
+ GeoPositionErrorCallback errorCallback,
+ UniquePtr<PositionOptions>&& options)
+{
+ if (mPendingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // After this we hand over ownership of options to our nsGeolocationRequest.
+
+ // Count the number of requests per protocol/scheme.
+ Telemetry::Accumulate(Telemetry::GEOLOCATION_GETCURRENTPOSITION_SECURE_ORIGIN,
+ static_cast<uint8_t>(mProtocolType));
+
+ RefPtr<nsGeolocationRequest> request =
+ new nsGeolocationRequest(this, Move(callback), Move(errorCallback),
+ Move(options), static_cast<uint8_t>(mProtocolType),
+ false);
+
+ if (!sGeoEnabled) {
+ nsCOMPtr<nsIRunnable> ev = new RequestAllowEvent(false, request);
+ NS_DispatchToMainThread(ev);
+ return NS_OK;
+ }
+
+ if (!mOwner && !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mOwner) {
+ if (!RegisterRequestWithPrompt(request)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+ }
+
+ if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIRunnable> ev = new RequestAllowEvent(true, request);
+ NS_DispatchToMainThread(ev);
+
+ return NS_OK;
+}
+
+int32_t
+Geolocation::WatchPosition(PositionCallback& aCallback,
+ PositionErrorCallback* aErrorCallback,
+ const PositionOptions& aOptions,
+ ErrorResult& aRv)
+{
+ int32_t ret = 0;
+ nsresult rv = WatchPosition(GeoPositionCallback(&aCallback),
+ GeoPositionErrorCallback(aErrorCallback),
+ Move(CreatePositionOptionsCopy(aOptions)), &ret);
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+
+ return ret;
+}
+
+NS_IMETHODIMP
+Geolocation::WatchPosition(nsIDOMGeoPositionCallback *aCallback,
+ nsIDOMGeoPositionErrorCallback *aErrorCallback,
+ UniquePtr<PositionOptions>&& aOptions,
+ int32_t* aRv)
+{
+ NS_ENSURE_ARG_POINTER(aCallback);
+
+ return WatchPosition(GeoPositionCallback(aCallback),
+ GeoPositionErrorCallback(aErrorCallback),
+ Move(aOptions), aRv);
+}
+
+nsresult
+Geolocation::WatchPosition(GeoPositionCallback aCallback,
+ GeoPositionErrorCallback aErrorCallback,
+ UniquePtr<PositionOptions>&& aOptions,
+ int32_t* aRv)
+{
+ if (mWatchingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Count the number of requests per protocol/scheme.
+ Telemetry::Accumulate(Telemetry::GEOLOCATION_WATCHPOSITION_SECURE_ORIGIN,
+ static_cast<uint8_t>(mProtocolType));
+
+ // The watch ID:
+ *aRv = mLastWatchId++;
+
+ RefPtr<nsGeolocationRequest> request =
+ new nsGeolocationRequest(this, Move(aCallback), Move(aErrorCallback),
+ Move(aOptions),
+ static_cast<uint8_t>(mProtocolType), true, *aRv);
+
+ if (!sGeoEnabled) {
+ nsCOMPtr<nsIRunnable> ev = new RequestAllowEvent(false, request);
+ NS_DispatchToMainThread(ev);
+ return NS_OK;
+ }
+
+ if (!mOwner && !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mOwner) {
+ if (!RegisterRequestWithPrompt(request))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_OK;
+ }
+
+ if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ request->Allow(JS::UndefinedHandleValue);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Geolocation::ClearWatch(int32_t aWatchId)
+{
+ if (aWatchId < 0) {
+ return NS_OK;
+ }
+
+ if (!mClearedWatchIDs.Contains(aWatchId)) {
+ mClearedWatchIDs.AppendElement(aWatchId);
+ }
+
+ for (uint32_t i = 0, length = mWatchingCallbacks.Length(); i < length; ++i) {
+ if (mWatchingCallbacks[i]->WatchId() == aWatchId) {
+ mWatchingCallbacks[i]->Shutdown();
+ RemoveRequest(mWatchingCallbacks[i]);
+ mClearedWatchIDs.RemoveElement(aWatchId);
+ break;
+ }
+ }
+
+ // make sure we also search through the pending requests lists for
+ // watches to clear...
+ for (uint32_t i = 0, length = mPendingRequests.Length(); i < length; ++i) {
+ if (mPendingRequests[i]->IsWatch() &&
+ (mPendingRequests[i]->WatchId() == aWatchId)) {
+ mPendingRequests[i]->Shutdown();
+ mPendingRequests.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+Geolocation::WindowOwnerStillExists()
+{
+ // an owner was never set when Geolocation
+ // was created, which means that this object
+ // is being used without a window.
+ if (mOwner == nullptr) {
+ return true;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mOwner);
+
+ if (window) {
+ nsPIDOMWindowOuter* outer = window->GetOuterWindow();
+ if (!outer || outer->GetCurrentInnerWindow() != window ||
+ outer->Closed()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void
+Geolocation::NotifyAllowedRequest(nsGeolocationRequest* aRequest)
+{
+ if (aRequest->IsWatch()) {
+ mWatchingCallbacks.AppendElement(aRequest);
+ } else {
+ mPendingCallbacks.AppendElement(aRequest);
+ }
+}
+
+bool
+Geolocation::RegisterRequestWithPrompt(nsGeolocationRequest* request)
+{
+ if (Preferences::GetBool("geo.prompt.testing", false)) {
+ bool allow = Preferences::GetBool("geo.prompt.testing.allow", false);
+ nsCOMPtr<nsIRunnable> ev = new RequestAllowEvent(allow, request);
+ NS_DispatchToMainThread(ev);
+ return true;
+ }
+
+ nsCOMPtr<nsIRunnable> ev = new RequestPromptEvent(request, mOwner);
+ NS_DispatchToMainThread(ev);
+ return true;
+}
+
+JSObject*
+Geolocation::WrapObject(JSContext *aCtx, JS::Handle<JSObject*> aGivenProto)
+{
+ return GeolocationBinding::Wrap(aCtx, this, aGivenProto);
+}