diff options
author | Pale Moon <git-repo@palemoon.org> | 2016-09-01 13:39:08 +0200 |
---|---|---|
committer | Pale Moon <git-repo@palemoon.org> | 2016-09-01 13:39:08 +0200 |
commit | 3d8ce1a11a7347cc94a937719c4bc8df46fb8d14 (patch) | |
tree | 8c26ca375a6312751c00a27e1653fb6f189f0463 /dom/geolocation | |
parent | e449bdb1ec3a82f204bffdd9c3c54069d086eee3 (diff) | |
download | palemoon-gre-3d8ce1a11a7347cc94a937719c4bc8df46fb8d14.tar.gz |
Base import of Tycho code (warning: huge commit)
Diffstat (limited to 'dom/geolocation')
-rw-r--r-- | dom/geolocation/moz.build | 54 | ||||
-rw-r--r-- | dom/geolocation/nsGeoGridFuzzer.cpp | 135 | ||||
-rw-r--r-- | dom/geolocation/nsGeoGridFuzzer.h | 25 | ||||
-rw-r--r-- | dom/geolocation/nsGeoPosition.cpp | 262 | ||||
-rw-r--r-- | dom/geolocation/nsGeoPosition.h | 145 | ||||
-rw-r--r-- | dom/geolocation/nsGeoPositionIPCSerialiser.h | 150 | ||||
-rw-r--r-- | dom/geolocation/nsGeolocation.cpp | 1627 | ||||
-rw-r--r-- | dom/geolocation/nsGeolocation.h | 258 | ||||
-rw-r--r-- | dom/geolocation/nsGeolocationSettings.cpp | 464 | ||||
-rw-r--r-- | dom/geolocation/nsGeolocationSettings.h | 165 |
10 files changed, 3285 insertions, 0 deletions
diff --git a/dom/geolocation/moz.build b/dom/geolocation/moz.build new file mode 100644 index 000000000..ba34983a4 --- /dev/null +++ b/dom/geolocation/moz.build @@ -0,0 +1,54 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS += [ + 'nsGeolocationSettings.h', + 'nsGeoPosition.h', + 'nsGeoPositionIPCSerialiser.h', +] + +SOURCES += [ + 'nsGeolocation.cpp', +] + +UNIFIED_SOURCES += [ + 'nsGeoGridFuzzer.cpp', + 'nsGeolocationSettings.cpp', + 'nsGeoPosition.cpp', +] + +FAIL_ON_WARNINGS = True + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '/dom/base', + '/dom/ipc', +] + +if CONFIG['MOZ_ENABLE_QT5GEOPOSITION']: + LOCAL_INCLUDES += [ + '/dom/system/qt', + ] + CXXFLAGS += CONFIG['MOZ_QT_CFLAGS'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': + LOCAL_INCLUDES += [ + '/dom/system/android', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': + LOCAL_INCLUDES += [ + '/dom/system/gonk', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + LOCAL_INCLUDES += [ + '/dom/system/mac', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + LOCAL_INCLUDES += [ + '/dom/system/windows', + ] diff --git a/dom/geolocation/nsGeoGridFuzzer.cpp b/dom/geolocation/nsGeoGridFuzzer.cpp new file mode 100644 index 000000000..d0be94936 --- /dev/null +++ b/dom/geolocation/nsGeoGridFuzzer.cpp @@ -0,0 +1,135 @@ +/* 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 <math.h> +#include "nsGeoGridFuzzer.h" +#include "nsGeoPosition.h" + + +#ifdef MOZ_APPROX_LOCATION + +/* The following constants are taken from the World Geodetic System 1984 (WGS84) + * reference model for the earth ellipsoid [1]. The values in the model are + * an accepted standard for GPS and other navigational systems. + * + * [1] http://www.oosa.unvienna.org/pdf/icg/2012/template/WGS_84.pdf + */ +#define WGS84_a (6378137.0) // equitorial axis +#define WGS84_b (6356752.314245179) // polar axis (a * (1-f)) +#define WGS84_f (1.0/298.257223563) // inverse flattening +#define WGS84_EPSILON (5.72957795e-9) // 1e-10 radians in degrees +#define sq(f) ((f) * (f)) +#define sign(f) (((f) < 0) ? -1 : 1) + +/* if you have an ellipsoid with semi-major axis A and semi-minor axis B, the + * radius at angle phi along the semi-major axis can be calculated with this + * formula. by using the WGS84 values for A and B, we calculate the radius of + * earth, given the angle of latitude, phi.*/ +#define LON_RADIUS(phi) (sqrt((sq(sq(WGS84_a) * cos(phi)) + sq(sq(WGS84_b) * sin(phi))) / \ + (sq(WGS84_a * cos(phi)) + sq(WGS84_b * sin(phi))))) +/* the radius of earth changes as a function of latitude, to simplify I am + * assuming the fixed radius of the earth halfway between the poles and the + * equator. this is calculated from LON_RADIUS(M_PI/4), or the radius at + * 45 degrees N.*/ +#define LAT_RADIUS (6367489.543863) + +/* This function figures out the latitudinal grid square that the given + * latitude coordinate falls into and then returns the latitudinal center of + * that grid square. It handles the proper wrapping at the poles +/- 90 + * (e.g. +95 wraps to +85 and -95 wraps to -85) */ +static double GridAlgorithmLat(int32_t aDistMeters, double aLatDeg) +{ + /* switch to radians */ + double phi = (aLatDeg * M_PI) / 180; + + /* properly wrap the latitude */ + phi = atan(sin(phi) / fabs(cos(phi))); + + /* calculate grid size in radians */ + double gridSizeRad = aDistMeters / LAT_RADIUS; + + /* find the southern edge, in radians, of the grid cell, then add half of a + * grid cell to find the center latitude in radians */ + double gridCenterPhi = gridSizeRad * floor(phi / gridSizeRad) + gridSizeRad / 2; + + /* properly wrap it and return it in degrees */ + return atan(sin(gridCenterPhi) / fabs(cos(gridCenterPhi))) * (180.0 / M_PI); +} + +/* This function figures out the longitudinal grid square that the given longitude + * coordinate falls into and then returns the longitudinal center of that grid + * square. It handles the proper wrapping at +/- 180 (e.g. +185 wraps to -175 + * and -185 wraps to +175) */ +static double GridAlgorithmLon(int32_t aDistMeters, double aLatDeg, double aLonDeg) +{ + /* switch to radians */ + double phi = (aLatDeg * M_PI) / 180; + double theta = (aLonDeg * M_PI) / 180; + + /* properly wrap the lat/lon */ + phi = atan(sin(phi) / fabs(cos(phi))); + theta = atan2(sin(theta), cos(theta)); + + /* calculate grid size in radians */ + double gridSizeRad = aDistMeters / LON_RADIUS(phi); + + /* find the western edge, in radians, of the grid cell, then add half of a + * grid cell to find the center longitude in radians */ + double gridCenterTheta = gridSizeRad * floor(theta / gridSizeRad) + gridSizeRad / 2; + + /* properly wrap it and return it in degrees */ + return atan2(sin(gridCenterTheta), cos(gridCenterTheta)) * (180.0 / M_PI); +} + +/* This function takes the grid size and the graticule coordinates of a + * location and calculates which grid cell the coordinates fall within and + * then returns the coordinates of the geographical center of the grid square. + */ +static void CalculateGridCoords(int32_t aDistKm, double& aLatDeg, double& aLonDeg) +{ + // a grid size of 0 is the same as precise + if (aDistKm == 0) { + return; + } + aLonDeg = GridAlgorithmLon(aDistKm * 1000, aLatDeg, aLonDeg); + aLatDeg = GridAlgorithmLat(aDistKm * 1000, aLatDeg); +} + +already_AddRefed<nsIDOMGeoPosition> +nsGeoGridFuzzer::FuzzLocation(const GeolocationSetting & aSetting, + nsIDOMGeoPosition * aPosition) +{ + if (!aPosition) { + return nullptr; + } + + nsCOMPtr<nsIDOMGeoPositionCoords> coords; + nsresult rv = aPosition->GetCoords(getter_AddRefs(coords)); + NS_ENSURE_SUCCESS(rv, nullptr); + if (!coords) { + return nullptr; + } + + double lat = 0.0, lon = 0.0; + coords->GetLatitude(&lat); + coords->GetLongitude(&lon); + + // adjust lat/lon to be the center of the grid square + CalculateGridCoords(aSetting.GetApproxDistance(), lat, lon); + GPSLOG("approximate location with delta %d is %f, %f", + aSetting.GetApproxDistance(), lat, lon); + + // reusing the timestamp + DOMTimeStamp ts; + rv = aPosition->GetTimestamp(&ts); + NS_ENSURE_SUCCESS(rv, nullptr); + + // return a position at sea level, N heading, 0 speed, 0 error. + nsRefPtr<nsGeoPosition> pos = new nsGeoPosition(lat, lon, 0.0, 0.0, + 0.0, 0.0, 0.0, ts); + return pos.forget(); +} + +#endif diff --git a/dom/geolocation/nsGeoGridFuzzer.h b/dom/geolocation/nsGeoGridFuzzer.h new file mode 100644 index 000000000..2a6f6d6bc --- /dev/null +++ b/dom/geolocation/nsGeoGridFuzzer.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsGeoGridFuzzer_h +#define nsGeoGridFuzzer_h + +#include "nsCOMPtr.h" +#include "nsIDOMGeoPosition.h" +#include "nsGeolocationSettings.h" + +class nsGeoGridFuzzer final +{ +public: + + static already_AddRefed<nsIDOMGeoPosition> + FuzzLocation(const GeolocationSetting& aSetting, nsIDOMGeoPosition* aPosition); + +private: + nsGeoGridFuzzer() {} // can't construct + nsGeoGridFuzzer(const nsGeoGridFuzzer&) {} // can't copy +}; + +#endif diff --git a/dom/geolocation/nsGeoPosition.cpp b/dom/geolocation/nsGeoPosition.cpp new file mode 100644 index 000000000..b92280784 --- /dev/null +++ b/dom/geolocation/nsGeoPosition.cpp @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsGeoPosition.h" + +#include "mozilla/dom/PositionBinding.h" +#include "mozilla/dom/CoordinatesBinding.h" + +//////////////////////////////////////////////////// +// nsGeoPositionCoords +//////////////////////////////////////////////////// +nsGeoPositionCoords::nsGeoPositionCoords(double aLat, double aLong, + double aAlt, double aHError, + double aVError, double aHeading, + double aSpeed) + : mLat(aLat) + , mLong(aLong) + , mAlt(aAlt) + , mHError(aHError) + , mVError(aVError) + , mHeading(aHeading) + , mSpeed(aSpeed) +{ +} + +nsGeoPositionCoords::~nsGeoPositionCoords() +{ +} + +NS_INTERFACE_MAP_BEGIN(nsGeoPositionCoords) +NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMGeoPositionCoords) +NS_INTERFACE_MAP_ENTRY(nsIDOMGeoPositionCoords) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsGeoPositionCoords) +NS_IMPL_RELEASE(nsGeoPositionCoords) + +NS_IMETHODIMP +nsGeoPositionCoords::GetLatitude(double *aLatitude) +{ + *aLatitude = mLat; + return NS_OK; +} + +NS_IMETHODIMP +nsGeoPositionCoords::GetLongitude(double *aLongitude) +{ + *aLongitude = mLong; + return NS_OK; +} + +NS_IMETHODIMP +nsGeoPositionCoords::GetAltitude(double *aAltitude) +{ + *aAltitude = mAlt; + return NS_OK; +} + +NS_IMETHODIMP +nsGeoPositionCoords::GetAccuracy(double *aAccuracy) +{ + *aAccuracy = mHError; + return NS_OK; +} + +NS_IMETHODIMP +nsGeoPositionCoords::GetAltitudeAccuracy(double *aAltitudeAccuracy) +{ + *aAltitudeAccuracy = mVError; + return NS_OK; +} + +NS_IMETHODIMP +nsGeoPositionCoords::GetHeading(double *aHeading) +{ + *aHeading = mHeading; + return NS_OK; +} + +NS_IMETHODIMP +nsGeoPositionCoords::GetSpeed(double *aSpeed) +{ + *aSpeed = mSpeed; + return NS_OK; +} + +//////////////////////////////////////////////////// +// nsGeoPosition +//////////////////////////////////////////////////// + +nsGeoPosition::nsGeoPosition(double aLat, double aLong, + double aAlt, double aHError, + double aVError, double aHeading, + double aSpeed, long long aTimestamp) : + mTimestamp(aTimestamp) +{ + mCoords = new nsGeoPositionCoords(aLat, aLong, + aAlt, aHError, + aVError, aHeading, + aSpeed); +} + +nsGeoPosition::nsGeoPosition(nsIDOMGeoPositionCoords *aCoords, + long long aTimestamp) : + mTimestamp(aTimestamp), + mCoords(aCoords) +{ +} + +nsGeoPosition::nsGeoPosition(nsIDOMGeoPositionCoords *aCoords, + DOMTimeStamp aTimestamp) : + mTimestamp(aTimestamp), + mCoords(aCoords) +{ +} + +nsGeoPosition::~nsGeoPosition() +{ +} + +NS_INTERFACE_MAP_BEGIN(nsGeoPosition) +NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMGeoPosition) +NS_INTERFACE_MAP_ENTRY(nsIDOMGeoPosition) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsGeoPosition) +NS_IMPL_RELEASE(nsGeoPosition) + +NS_IMETHODIMP +nsGeoPosition::GetTimestamp(DOMTimeStamp* aTimestamp) +{ + *aTimestamp = mTimestamp; + return NS_OK; +} + +NS_IMETHODIMP +nsGeoPosition::GetCoords(nsIDOMGeoPositionCoords * *aCoords) +{ + NS_IF_ADDREF(*aCoords = mCoords); + return NS_OK; +} + +namespace mozilla { +namespace dom { + + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Position, mParent, mCoordinates) +NS_IMPL_CYCLE_COLLECTING_ADDREF(Position) +NS_IMPL_CYCLE_COLLECTING_RELEASE(Position) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Position) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +Position::Position(nsISupports* aParent, nsIDOMGeoPosition* aGeoPosition) + : mParent(aParent) + , mGeoPosition(aGeoPosition) +{ +} + +Position::~Position() +{ +} + +nsISupports* +Position::GetParentObject() const +{ + return mParent; +} + +JSObject* +Position::WrapObject(JSContext* aCx) +{ + return PositionBinding::Wrap(aCx, this); +} + +Coordinates* +Position::Coords() +{ + if (!mCoordinates) { + nsCOMPtr<nsIDOMGeoPositionCoords> coords; + mGeoPosition->GetCoords(getter_AddRefs(coords)); + MOZ_ASSERT(coords, "coords should not be null"); + + mCoordinates = new Coordinates(this, coords); + } + + return mCoordinates; +} + +uint64_t +Position::Timestamp() const +{ + uint64_t rv; + + mGeoPosition->GetTimestamp(&rv); + return rv; +} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Coordinates, mPosition) +NS_IMPL_CYCLE_COLLECTING_ADDREF(Coordinates) +NS_IMPL_CYCLE_COLLECTING_RELEASE(Coordinates) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Coordinates) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +Coordinates::Coordinates(Position* aPosition, nsIDOMGeoPositionCoords* aCoords) + : mPosition(aPosition) + , mCoords(aCoords) +{ +} + +Coordinates::~Coordinates() +{ +} + +Position* +Coordinates::GetParentObject() const +{ + return mPosition; +} + +JSObject* +Coordinates::WrapObject(JSContext* aCx) +{ + return CoordinatesBinding::Wrap(aCx, this); +} + +#define GENERATE_COORDS_WRAPPED_GETTER(name) \ +double \ +Coordinates::name() const \ +{ \ + double rv; \ + mCoords->Get##name(&rv); \ + return rv; \ +} + +#define GENERATE_COORDS_WRAPPED_GETTER_NULLABLE(name) \ +Nullable<double> \ +Coordinates::Get##name() const \ +{ \ + double rv; \ + mCoords->Get##name(&rv); \ + return Nullable<double>(rv); \ +} + +GENERATE_COORDS_WRAPPED_GETTER(Latitude) +GENERATE_COORDS_WRAPPED_GETTER(Longitude) +GENERATE_COORDS_WRAPPED_GETTER_NULLABLE(Altitude) +GENERATE_COORDS_WRAPPED_GETTER(Accuracy) +GENERATE_COORDS_WRAPPED_GETTER_NULLABLE(AltitudeAccuracy) +GENERATE_COORDS_WRAPPED_GETTER_NULLABLE(Heading) +GENERATE_COORDS_WRAPPED_GETTER_NULLABLE(Speed) + +#undef GENERATE_COORDS_WRAPPED_GETTER +#undef GENERATE_COORDS_WRAPPED_GETTER_NULLABLE + +} // namespace dom +} // namespace mozilla diff --git a/dom/geolocation/nsGeoPosition.h b/dom/geolocation/nsGeoPosition.h new file mode 100644 index 000000000..3b569ee99 --- /dev/null +++ b/dom/geolocation/nsGeoPosition.h @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsGeoPosition_h +#define nsGeoPosition_h + +#include "nsAutoPtr.h" +#include "nsIDOMGeoPositionCoords.h" +#include "nsIDOMGeoPosition.h" +#include "nsString.h" +#include "mozilla/Attributes.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "mozilla/dom/Nullable.h" +#include "js/TypeDecls.h" + +//////////////////////////////////////////////////// +// nsGeoPositionCoords +//////////////////////////////////////////////////// + +/** + * Simple object that holds a single point in space. + */ +class nsGeoPositionCoords final : public nsIDOMGeoPositionCoords +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDOMGEOPOSITIONCOORDS + + nsGeoPositionCoords(double aLat, double aLong, + double aAlt, double aHError, + double aVError, double aHeading, + double aSpeed); +private: + ~nsGeoPositionCoords(); + const double mLat, mLong, mAlt, mHError, mVError, mHeading, mSpeed; +}; + + +//////////////////////////////////////////////////// +// nsGeoPosition +//////////////////////////////////////////////////// + +class nsGeoPosition final : public nsIDOMGeoPosition +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDOMGEOPOSITION + + nsGeoPosition(double aLat, double aLong, + double aAlt, double aHError, + double aVError, double aHeading, + double aSpeed, long long aTimestamp); + + + nsGeoPosition(nsIDOMGeoPositionCoords *aCoords, + long long aTimestamp); + + nsGeoPosition(nsIDOMGeoPositionCoords *aCoords, + DOMTimeStamp aTimestamp); + +private: + ~nsGeoPosition(); + long long mTimestamp; + nsRefPtr<nsIDOMGeoPositionCoords> mCoords; +}; + +//////////////////////////////////////////////////// +// WebIDL wrappers for the classes above +//////////////////////////////////////////////////// + +namespace mozilla { +namespace dom { + +class Coordinates; + +class Position final : public nsISupports, + public nsWrapperCache +{ + ~Position(); + +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Position) + +public: + Position(nsISupports* aParent, nsIDOMGeoPosition* aGeoPosition); + + nsISupports* GetParentObject() const; + + virtual JSObject* WrapObject(JSContext* aCx) override; + + Coordinates* Coords(); + + uint64_t Timestamp() const; + + nsIDOMGeoPosition* GetWrappedGeoPosition() { return mGeoPosition; } + +private: + nsRefPtr<Coordinates> mCoordinates; + nsCOMPtr<nsISupports> mParent; + nsCOMPtr<nsIDOMGeoPosition> mGeoPosition; +}; + +class Coordinates final : public nsISupports, + public nsWrapperCache +{ + ~Coordinates(); + +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Coordinates) + +public: + Coordinates(Position* aPosition, nsIDOMGeoPositionCoords* aCoords); + + Position* GetParentObject() const; + + virtual JSObject* WrapObject(JSContext* aCx) override; + + double Latitude() const; + + double Longitude() const; + + Nullable<double> GetAltitude() const; + + double Accuracy() const; + + Nullable<double> GetAltitudeAccuracy() const; + + Nullable<double> GetHeading() const; + + Nullable<double> GetSpeed() const; +private: + nsRefPtr<Position> mPosition; + nsCOMPtr<nsIDOMGeoPositionCoords> mCoords; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* nsGeoPosition_h */ + diff --git a/dom/geolocation/nsGeoPositionIPCSerialiser.h b/dom/geolocation/nsGeoPositionIPCSerialiser.h new file mode 100644 index 000000000..7fbbabc22 --- /dev/null +++ b/dom/geolocation/nsGeoPositionIPCSerialiser.h @@ -0,0 +1,150 @@ +/* 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/. */ + +#ifndef dom_src_geolocation_IPC_serialiser +#define dom_src_geolocation_IPC_serialiser + +#include "ipc/IPCMessageUtils.h" +#include "nsGeoPosition.h" +#include "nsIDOMGeoPosition.h" + +typedef nsIDOMGeoPosition* GeoPosition; + +namespace IPC { + +template <> +struct ParamTraits<nsIDOMGeoPositionCoords*> +{ + typedef nsIDOMGeoPositionCoords* paramType; + + // Function to serialize a geoposition + static void Write(Message *aMsg, const paramType& aParam) + { + bool isNull = !aParam; + WriteParam(aMsg, isNull); + // If it is a null object, then we are done + if (isNull) return; + + double coordData; + + aParam->GetLatitude(&coordData); + WriteParam(aMsg, coordData); + + aParam->GetLongitude(&coordData); + WriteParam(aMsg, coordData); + + aParam->GetAltitude(&coordData); + WriteParam(aMsg, coordData); + + aParam->GetAccuracy(&coordData); + WriteParam(aMsg, coordData); + + aParam->GetAltitudeAccuracy(&coordData); + WriteParam(aMsg, coordData); + + aParam->GetHeading(&coordData); + WriteParam(aMsg, coordData); + + aParam->GetSpeed(&coordData); + WriteParam(aMsg, coordData); + } + + // Function to de-serialize a geoposition + static bool Read(const Message* aMsg, void **aIter, paramType* aResult) + { + // Check if it is the null pointer we have transfered + bool isNull; + if (!ReadParam(aMsg, aIter, &isNull)) return false; + + if (isNull) { + *aResult = 0; + return true; + } + + double latitude; + double longitude; + double altitude; + double accuracy; + double altitudeAccuracy; + double heading; + double speed; + + // It's not important to us where it fails, but rather if it fails + if (!( ReadParam(aMsg, aIter, &latitude ) + && ReadParam(aMsg, aIter, &longitude ) + && ReadParam(aMsg, aIter, &altitude ) + && ReadParam(aMsg, aIter, &accuracy ) + && ReadParam(aMsg, aIter, &altitudeAccuracy ) + && ReadParam(aMsg, aIter, &heading ) + && ReadParam(aMsg, aIter, &speed ))) return false; + + // We now have all the data + *aResult = new nsGeoPositionCoords(latitude, /* aLat */ + longitude, /* aLong */ + altitude, /* aAlt */ + accuracy, /* aHError */ + altitudeAccuracy, /* aVError */ + heading, /* aHeading */ + speed /* aSpeed */ + ); + return true; + + } + +}; + +template <> +struct ParamTraits<nsIDOMGeoPosition*> +{ + typedef nsIDOMGeoPosition* paramType; + + // Function to serialize a geoposition + static void Write(Message *aMsg, const paramType& aParam) + { + bool isNull = !aParam; + WriteParam(aMsg, isNull); + // If it is a null object, then we are done + if (isNull) return; + + DOMTimeStamp timeStamp; + aParam->GetTimestamp(&timeStamp); + WriteParam(aMsg, timeStamp); + + nsCOMPtr<nsIDOMGeoPositionCoords> coords; + aParam->GetCoords(getter_AddRefs(coords)); + WriteParam(aMsg, coords.get()); + } + + // Function to de-serialize a geoposition + static bool Read(const Message* aMsg, void **aIter, paramType* aResult) + { + // Check if it is the null pointer we have transfered + bool isNull; + if (!ReadParam(aMsg, aIter, &isNull)) return false; + + if (isNull) { + *aResult = 0; + return true; + } + + DOMTimeStamp timeStamp; + nsIDOMGeoPositionCoords* coords = nullptr; + + // It's not important to us where it fails, but rather if it fails + if (!ReadParam(aMsg, aIter, &timeStamp) || + !ReadParam(aMsg, aIter, &coords)) { + nsCOMPtr<nsIDOMGeoPositionCoords> tmpcoords = coords; + return false; + } + + *aResult = new nsGeoPosition(coords, timeStamp); + + return true; + }; + +}; + +} + +#endif diff --git a/dom/geolocation/nsGeolocation.cpp b/dom/geolocation/nsGeolocation.cpp new file mode 100644 index 000000000..eb9cd5c16 --- /dev/null +++ b/dom/geolocation/nsGeolocation.cpp @@ -0,0 +1,1627 @@ +/* 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 "nsISettingsService.h" + +#include "nsGeolocation.h" +#include "nsGeoGridFuzzer.h" +#include "nsGeolocationSettings.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/dom/PermissionMessageUtils.h" +#include "mozilla/dom/SettingChangeNotificationBinding.h" + +#include "nsJSUtils.h" +#include "prdtoa.h" + +class nsIPrincipal; + +#ifdef MOZ_ENABLE_QT5GEOPOSITION +#include "QTMLocationProvider.h" +#endif + +#ifdef MOZ_WIDGET_ANDROID +#include "AndroidLocationProvider.h" +#endif + +#ifdef MOZ_WIDGET_GONK +#include "GonkGPSGeolocationProvider.h" +#endif + +#ifdef MOZ_WIDGET_COCOA +#include "CoreLocationLocationProvider.h" +#endif + +#ifdef XP_WIN +#include "WindowsLocationProvider.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 + +// the geolocation enabled setting +#define GEO_SETTINGS_ENABLED "geolocation.enabled" + +using mozilla::unused; // <snicker> +using namespace mozilla; +using namespace mozilla::dom; + +class nsGeolocationRequest final + : public nsIContentPermissionRequest + , public nsITimerCallback + , public nsIGeolocationUpdate +{ + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSICONTENTPERMISSIONREQUEST + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSIGEOLOCATIONUPDATE + + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsGeolocationRequest, nsIContentPermissionRequest) + + nsGeolocationRequest(Geolocation* aLocator, + const GeoPositionCallback& aCallback, + const GeoPositionErrorCallback& aErrorCallback, + PositionOptions* aOptions, + bool aWatchPositionRequest = false, + int32_t aWatchId = 0); + 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(); + + already_AddRefed<nsIDOMGeoPosition> AdjustedLocation(nsIDOMGeoPosition*); + + bool mIsWatchPositionRequest; + + nsCOMPtr<nsITimer> mTimeoutTimer; + GeoPositionCallback mCallback; + GeoPositionErrorCallback mErrorCallback; + nsAutoPtr<PositionOptions> mOptions; + + nsRefPtr<Geolocation> mLocator; + + int32_t mWatchId; + bool mShutdown; +}; + +static PositionOptions* +CreatePositionOptionsCopy(const PositionOptions& aOptions) +{ + nsAutoPtr<PositionOptions> geoOptions(new PositionOptions()); + + geoOptions->mEnableHighAccuracy = aOptions.mEnableHighAccuracy; + geoOptions->mMaximumAge = aOptions.mMaximumAge; + geoOptions->mTimeout = aOptions.mTimeout; + + return geoOptions.forget(); +} + +class GeolocationSettingsCallback : public nsISettingsServiceCallback +{ + virtual ~GeolocationSettingsCallback() { + MOZ_COUNT_DTOR(GeolocationSettingsCallback); + } + +public: + NS_DECL_ISUPPORTS + + GeolocationSettingsCallback() { + MOZ_COUNT_CTOR(GeolocationSettingsCallback); + } + + NS_IMETHOD Handle(const nsAString& aName, JS::Handle<JS::Value> aResult) override + { + MOZ_ASSERT(NS_IsMainThread()); + + if (aName.EqualsASCII(GEO_SETTINGS_ENABLED)) { + // The geolocation is enabled by default: + bool value = true; + if (aResult.isBoolean()) { + value = aResult.toBoolean(); + } + + GPSLOG("%s set to %s", + NS_ConvertUTF16toUTF8(aName).get(), + (value ? "ENABLED" : "DISABLED")); + MozSettingValue(value); + + } else { + nsRefPtr<nsGeolocationSettings> gs = nsGeolocationSettings::GetGeolocationSettings(); + if (gs) { + gs->HandleGeolocationSettingsChange(aName, aResult); + } + } + + return NS_OK; + } + + NS_IMETHOD HandleError(const nsAString& aName) override + { + if (aName.EqualsASCII(GEO_SETTINGS_ENABLED)) { + GPSLOG("Unable to get value for '" GEO_SETTINGS_ENABLED "'"); + + // Default it's enabled: + MozSettingValue(true); + } else { + nsRefPtr<nsGeolocationSettings> gs = nsGeolocationSettings::GetGeolocationSettings(); + if (gs) { + gs->HandleGeolocationSettingsError(aName); + } + } + + return NS_OK; + } + + void MozSettingValue(const bool aValue) + { + nsRefPtr<nsGeolocationService> gs = nsGeolocationService::GetGeolocationService(); + if (gs) { + gs->HandleMozsettingValue(aValue); + } + } +}; + +NS_IMPL_ISUPPORTS(GeolocationSettingsCallback, nsISettingsServiceCallback) + +class RequestPromptEvent : public nsRunnable +{ +public: + RequestPromptEvent(nsGeolocationRequest* aRequest, nsWeakPtr aWindow) + : mRequest(aRequest) + , mWindow(aWindow) + { + } + + NS_IMETHOD Run() + { + nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow); + nsContentPermissionUtils::AskPermission(mRequest, window); + return NS_OK; + } + +private: + nsRefPtr<nsGeolocationRequest> mRequest; + nsWeakPtr mWindow; +}; + +class RequestAllowEvent : public nsRunnable +{ +public: + RequestAllowEvent(int allow, nsGeolocationRequest* request) + : mAllow(allow), + mRequest(request) + { + } + + NS_IMETHOD Run() { + if (mAllow) { + mRequest->Allow(JS::UndefinedHandleValue); + } else { + mRequest->Cancel(); + } + return NS_OK; + } + +private: + bool mAllow; + nsRefPtr<nsGeolocationRequest> mRequest; +}; + +class RequestSendLocationEvent : public nsRunnable +{ +public: + RequestSendLocationEvent(nsIDOMGeoPosition* aPosition, + nsGeolocationRequest* aRequest) + : mPosition(aPosition), + mRequest(aRequest) + { + } + + NS_IMETHOD Run() { + mRequest->SendLocation(mPosition); + return NS_OK; + } + +private: + nsCOMPtr<nsIDOMGeoPosition> mPosition; + nsRefPtr<nsGeolocationRequest> mRequest; + nsRefPtr<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) +{ + return PositionErrorBinding::Wrap(aCx, this); +} + +void +PositionError::NotifyCallback(const GeoPositionErrorCallback& aCallback) +{ + nsAutoMicroTask mt; + if (aCallback.HasWebIDLCallback()) { + PositionErrorCallback* callback = aCallback.GetWebIDLCallback(); + + if (callback) { + ErrorResult err; + callback->Call(*this, err); + } + } else { + nsIDOMGeoPositionErrorCallback* callback = aCallback.GetXPCOMCallback(); + if (callback) { + callback->HandleEvent(this); + } + } +} +//////////////////////////////////////////////////// +// nsGeolocationRequest +//////////////////////////////////////////////////// + +nsGeolocationRequest::nsGeolocationRequest(Geolocation* aLocator, + const GeoPositionCallback& aCallback, + const GeoPositionErrorCallback& aErrorCallback, + PositionOptions* aOptions, + bool aWatchPositionRequest, + int32_t aWatchId) + : mIsWatchPositionRequest(aWatchPositionRequest), + mCallback(aCallback), + mErrorCallback(aErrorCallback), + mOptions(aOptions), + mLocator(aLocator), + mWatchId(aWatchId), + mShutdown(false) +{ +} + +nsGeolocationRequest::~nsGeolocationRequest() +{ +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGeolocationRequest) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest) + NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest) + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) + 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) + +NS_IMETHODIMP +nsGeolocationRequest::Notify(nsITimer* aTimer) +{ + StopTimeoutTimer(); + NotifyErrorAndShutdown(nsIDOMGeoPositionError::TIMEOUT); + return NS_OK; +} + +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(nsIDOMWindow * *aRequestingWindow) +{ + NS_ENSURE_ARG_POINTER(aRequestingWindow); + + nsCOMPtr<nsIDOMWindow> 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 (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 (mLocator->ClearPendingRequest(this)) { + return NS_OK; + } + + // Kick off the geo device, if it isn't already running + nsRefPtr<nsGeolocationService> gs = nsGeolocationService::GetGeolocationService(); + nsresult rv = gs->StartDevice(GetPrincipal()); + + if (NS_FAILED(rv)) { + // Location provider error + NotifyError(nsIDOMGeoPositionError::POSITION_UNAVAILABLE); + return NS_OK; + } + + 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); + } + + 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; +} + +void +nsGeolocationRequest::SetTimeoutTimer() +{ + StopTimeoutTimer(); + + int32_t timeout; + if (mOptions && (timeout = mOptions->mTimeout) != 0) { + + if (timeout < 0) { + timeout = 0; + } else if (timeout < 10) { + timeout = 10; + } + + mTimeoutTimer = do_CreateInstance("@mozilla.org/timer;1"); + mTimeoutTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT); + } +} + +void +nsGeolocationRequest::StopTimeoutTimer() +{ + if (mTimeoutTimer) { + mTimeoutTimer->Cancel(); + mTimeoutTimer = nullptr; + } +} + +static already_AddRefed<nsIDOMGeoPosition> +SynthesizeLocation(DOMTimeStamp aTimestamp, double aLatitude, double aLongitude) +{ + // return a position at sea level, N heading, 0 speed, 0 error. + nsRefPtr<nsGeoPosition> pos = new nsGeoPosition(aLatitude, aLongitude, + 0.0, 0.0, 0.0, 0.0, 0.0, + aTimestamp); + return pos.forget(); +} + + +already_AddRefed<nsIDOMGeoPosition> +nsGeolocationRequest::AdjustedLocation(nsIDOMGeoPosition *aPosition) +{ + nsCOMPtr<nsIDOMGeoPosition> pos = aPosition; + if (XRE_GetProcessType() == GoannaProcessType_Content) { + GPSLOG("child process just copying position"); + return pos.forget(); + } + + // get the settings cache + nsRefPtr<nsGeolocationSettings> gs = nsGeolocationSettings::GetGeolocationSettings(); + if (!gs) { + return pos.forget(); + } + + // make sure ALA is enabled + if (!gs->IsAlaEnabled()) { + GPSLOG("ALA is disabled, returning precise location"); + return pos.forget(); + } + + // look up the geolocation settings via the watch ID + DOMTimeStamp ts(PR_Now() / PR_USEC_PER_MSEC); + GeolocationSetting setting = gs->LookupGeolocationSetting(mWatchId); + switch (setting.GetType()) { + case GEO_ALA_TYPE_PRECISE: + GPSLOG("returning precise location watch ID: %d", mWatchId); + return pos.forget(); +#ifdef MOZ_APPROX_LOCATION + case GEO_ALA_TYPE_APPROX: + GPSLOG("returning approximate location for watch ID: %d", mWatchId); + return nsGeoGridFuzzer::FuzzLocation(setting, aPosition); +#endif + case GEO_ALA_TYPE_FIXED: + GPSLOG("returning fixed location for watch ID:: %d", mWatchId); + // use "now" as time stamp + return SynthesizeLocation(ts, setting.GetFixedLatitude(), + setting.GetFixedLongitude()); + case GEO_ALA_TYPE_NONE: + GPSLOG("returning no location for watch ID: %d", mWatchId); + // return nullptr so no location callback happens + return nullptr; + } + + return 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; + } + } + + nsRefPtr<Position> wrapped; + + if (aPosition) { + nsCOMPtr<nsIDOMGeoPositionCoords> coords; + aPosition->GetCoords(getter_AddRefs(coords)); + if (coords) { +#ifdef MOZ_GPS_DEBUG + double lat = 0.0, lon = 0.0; + coords->GetLatitude(&lat); + coords->GetLongitude(&lon); + GPSLOG("returning coordinates: %f, %f", lat, lon); +#endif + 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()) { + ErrorResult err; + PositionCallback* callback = mCallback.GetWebIDLCallback(); + + MOZ_ASSERT(callback); + callback->Call(*wrapped, err); + } else { + nsIDOMGeoPositionCallback* callback = mCallback.GetXPCOMCallback(); + + MOZ_ASSERT(callback); + callback->HandleEvent(aPosition); + } + + StopTimeoutTimer(); + 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<nsIDOMGeoPosition> pos = AdjustedLocation(aPosition); + nsCOMPtr<nsIRunnable> ev = new RequestSendLocationEvent(pos, this); + NS_DispatchToMainThread(ev); + return NS_OK; +} + +NS_IMETHODIMP +nsGeolocationRequest::LocationUpdatePending() +{ + if (!mTimeoutTimer) { + SetTimeoutTimer(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsGeolocationRequest::NotifyError(uint16_t aErrorCode) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsRefPtr<PositionError> positionError = new PositionError(mLocator, aErrorCode); + positionError->NotifyCallback(mErrorCallback); + return NS_OK; +} + +void +nsGeolocationRequest::Shutdown() +{ + MOZ_ASSERT(!mShutdown, "request shutdown twice"); + mShutdown = true; + + if (mTimeoutTimer) { + mTimeoutTimer->Cancel(); + mTimeoutTimer = nullptr; + } + + // 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) { + nsRefPtr<nsGeolocationService> gs = nsGeolocationService::GetGeolocationService(); + if (gs) { + gs->UpdateAccuracy(); + } + } +} + +//////////////////////////////////////////////////// +// 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 bool sGeoInitPending = 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_GetProcessType() == GoannaProcessType_Content) { + sGeoInitPending = false; + return NS_OK; + } + + // check if the geolocation service is enable from settings + nsCOMPtr<nsISettingsService> settings = + do_GetService("@mozilla.org/settingsService;1"); + + if (settings) { + nsCOMPtr<nsISettingsServiceLock> settingsLock; + nsresult rv = settings->CreateLock(nullptr, getter_AddRefs(settingsLock)); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr<GeolocationSettingsCallback> callback = new GeolocationSettingsCallback(); + rv = settingsLock->Get(GEO_SETTINGS_ENABLED, callback); + NS_ENSURE_SUCCESS(rv, rv); + + // look up the geolocation settings + callback = new GeolocationSettingsCallback(); + rv = settingsLock->Get(GEO_ALA_ENABLED, callback); + NS_ENSURE_SUCCESS(rv, rv); + callback = new GeolocationSettingsCallback(); + rv = settingsLock->Get(GEO_ALA_TYPE, callback); + NS_ENSURE_SUCCESS(rv, rv); +#ifdef MOZ_APPROX_LOCATION + callback = new GeolocationSettingsCallback(); + rv = settingsLock->Get(GEO_ALA_APPROX_DISTANCE, callback); + NS_ENSURE_SUCCESS(rv, rv); +#endif + callback = new GeolocationSettingsCallback(); + rv = settingsLock->Get(GEO_ALA_FIXED_COORDS, callback); + NS_ENSURE_SUCCESS(rv, rv); + callback = new GeolocationSettingsCallback(); + rv = settingsLock->Get(GEO_ALA_APP_SETTINGS, callback); + NS_ENSURE_SUCCESS(rv, rv); + callback = new GeolocationSettingsCallback(); + rv = settingsLock->Get(GEO_ALA_ALWAYS_PRECISE, callback); + NS_ENSURE_SUCCESS(rv, rv); + + } else { + // If we cannot obtain the settings service, we continue + // assuming that the geolocation is enabled: + sGeoInitPending = false; + } + + // geolocation service can be enabled -> now register observer + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (!obs) { + return NS_ERROR_FAILURE; + } + + obs->AddObserver(this, "quit-application", false); + obs->AddObserver(this, "mozsettings-changed", false); + +#ifdef MOZ_ENABLE_QT5GEOPOSITION + mProvider = new QTMLocationProvider(); +#endif + +#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_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)) { + 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() +{ +} + +void +nsGeolocationService::HandleMozsettingChanged(nsISupports* aSubject) +{ + // The string that we're interested in will be a JSON string that looks like: + // {"key":"gelocation.enabled","value":true} + + RootedDictionary<SettingChangeNotification> setting(nsContentUtils::RootingCxForThread()); + if (!WrappedJSToDictionary(aSubject, setting)) { + return; + } + if (!setting.mKey.EqualsASCII(GEO_SETTINGS_ENABLED)) { + return; + } + if (!setting.mValue.isBoolean()) { + return; + } + + GPSLOG("mozsetting changed: %s == %s", + NS_ConvertUTF16toUTF8(setting.mKey).get(), + (setting.mValue.toBoolean() ? "TRUE" : "FALSE")); + + HandleMozsettingValue(setting.mValue.toBoolean()); +} + +void +nsGeolocationService::HandleMozsettingValue(const bool aValue) +{ + if (!aValue) { + // turn things off + StopDevice(); + Update(nullptr); + mLastPosition.position = nullptr; + sGeoEnabled = false; + } else { + sGeoEnabled = true; + } + + if (sGeoInitPending) { + sGeoInitPending = false; + for (uint32_t i = 0, length = mGeolocators.Length(); i < length; ++i) { + mGeolocators[i]->ServiceReady(); + } + } +} + +NS_IMETHODIMP +nsGeolocationService::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + if (!strcmp("quit-application", aTopic)) { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "quit-application"); + obs->RemoveObserver(this, "mozsettings-changed"); + } + + for (uint32_t i = 0; i< mGeolocators.Length(); i++) { + mGeolocators[i]->Shutdown(); + } + StopDevice(); + + return NS_OK; + } + + if (!strcmp("mozsettings-changed", aTopic)) { + HandleMozsettingChanged(aSubject); + 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) +{ + SetCachedPosition(aSomewhere); + + for (uint32_t i = 0; i< mGeolocators.Length(); i++) { + mGeolocators[i]->Update(aSomewhere); + } + return NS_OK; +} + +NS_IMETHODIMP +nsGeolocationService::LocationUpdatePending() +{ + for (uint32_t i = 0; i< mGeolocators.Length(); i++) { + mGeolocators[i]->LocationUpdatePending(); + } + + 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 || sGeoInitPending) { + 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_GetProcessType() == GoannaProcessType_Content) { + 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", + MOZ_UTF16("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_GetProcessType() == GoannaProcessType_Content) { + ContentChild* cpc = ContentChild::GetSingleton(); + if (cpc->IsAlive()) { + cpc->SendSetGeolocationHigherAccuracy(highRequired); + } + return; + } + + if (!mHigherAccuracy && highRequired) { + mProvider->SetHighAccuracy(true); + } + + if (mHigherAccuracy && !highRequired) { + mProvider->SetHighAccuracy(false); + } + + mHigherAccuracy = highRequired; +} + +void +nsGeolocationService::StopDevice() +{ + if(mDisconnectTimer) { + mDisconnectTimer->Cancel(); + mDisconnectTimer = nullptr; + } + + if (XRE_GetProcessType() == GoannaProcessType_Content) { + 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", + MOZ_UTF16("shutdown")); +} + +StaticRefPtr<nsGeolocationService> nsGeolocationService::sService; + +already_AddRefed<nsGeolocationService> +nsGeolocationService::GetGeolocationService() +{ + nsRefPtr<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() +: mLastWatchId(0) +{ +} + +Geolocation::~Geolocation() +{ + if (mService) { + Shutdown(); + } +} + +nsresult +Geolocation::Init(nsIDOMWindow* aContentDom) +{ + // Remember the window + if (aContentDom) { + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aContentDom); + if (!window) { + return NS_ERROR_FAILURE; + } + + mOwner = do_GetWeakReference(window->GetCurrentInnerWindow()); + if (!mOwner) { + return NS_ERROR_FAILURE; + } + + // Grab the principal of the document + nsCOMPtr<nsIDocument> doc = window->GetDoc(); + if (!doc) { + return NS_ERROR_FAILURE; + } + + mPrincipal = doc->NodePrincipal(); + } + + // 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; +} + +nsIDOMWindow* +Geolocation::GetParentObject() const { + nsCOMPtr<nsPIDOMWindow> 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::LocationUpdatePending() +{ + // this event is only really interesting for watch callbacks + for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) { + mWatchingCallbacks[i]->LocationUpdatePending(); + } + + 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) +{ + GeoPositionCallback successCallback(&aCallback); + GeoPositionErrorCallback errorCallback(aErrorCallback); + + nsresult rv = GetCurrentPosition(successCallback, errorCallback, + CreatePositionOptionsCopy(aOptions)); + + if (NS_FAILED(rv)) { + aRv.Throw(rv); + } + + return; +} + +NS_IMETHODIMP +Geolocation::GetCurrentPosition(nsIDOMGeoPositionCallback* aCallback, + nsIDOMGeoPositionErrorCallback* aErrorCallback, + PositionOptions* aOptions) +{ + NS_ENSURE_ARG_POINTER(aCallback); + + GeoPositionCallback successCallback(aCallback); + GeoPositionErrorCallback errorCallback(aErrorCallback); + + return GetCurrentPosition(successCallback, errorCallback, aOptions); +} + +nsresult +Geolocation::GetCurrentPosition(GeoPositionCallback& callback, + GeoPositionErrorCallback& errorCallback, + PositionOptions *options) +{ + if (mPendingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsRefPtr<nsGeolocationRequest> request = new nsGeolocationRequest(this, + callback, + errorCallback, + options, + false); + + if (!sGeoEnabled) { + nsCOMPtr<nsIRunnable> ev = new RequestAllowEvent(false, request); + NS_DispatchToMainThread(ev); + return NS_OK; + } + + if (!mOwner && !nsContentUtils::IsCallerChrome()) { + return NS_ERROR_FAILURE; + } + + if (sGeoInitPending) { + mPendingRequests.AppendElement(request); + return NS_OK; + } + + return GetCurrentPositionReady(request); +} + +nsresult +Geolocation::GetCurrentPositionReady(nsGeolocationRequest* aRequest) +{ + if (mOwner) { + if (!RegisterRequestWithPrompt(aRequest)) { + return NS_ERROR_NOT_AVAILABLE; + } + + return NS_OK; + } + + if (!nsContentUtils::IsCallerChrome()) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIRunnable> ev = new RequestAllowEvent(true, aRequest); + NS_DispatchToMainThread(ev); + + return NS_OK; +} + +int32_t +Geolocation::WatchPosition(PositionCallback& aCallback, + PositionErrorCallback* aErrorCallback, + const PositionOptions& aOptions, + ErrorResult& aRv) +{ + int32_t ret; + GeoPositionCallback successCallback(&aCallback); + GeoPositionErrorCallback errorCallback(aErrorCallback); + + nsresult rv = WatchPosition(successCallback, errorCallback, + CreatePositionOptionsCopy(aOptions), &ret); + + if (NS_FAILED(rv)) { + aRv.Throw(rv); + } + + return ret; +} + +NS_IMETHODIMP +Geolocation::WatchPosition(nsIDOMGeoPositionCallback *aCallback, + nsIDOMGeoPositionErrorCallback *aErrorCallback, + PositionOptions *aOptions, + int32_t* aRv) +{ + NS_ENSURE_ARG_POINTER(aCallback); + + GeoPositionCallback successCallback(aCallback); + GeoPositionErrorCallback errorCallback(aErrorCallback); + + return WatchPosition(successCallback, errorCallback, aOptions, aRv); +} + +nsresult +Geolocation::WatchPosition(GeoPositionCallback& aCallback, + GeoPositionErrorCallback& aErrorCallback, + PositionOptions* aOptions, + int32_t* aRv) +{ + if (mWatchingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) { + return NS_ERROR_NOT_AVAILABLE; + } + + // The watch ID: + *aRv = mLastWatchId++; + + nsRefPtr<nsGeolocationRequest> request = new nsGeolocationRequest(this, + aCallback, + aErrorCallback, + aOptions, + true, + *aRv); + + if (!sGeoEnabled) { + GPSLOG("request allow event"); + nsCOMPtr<nsIRunnable> ev = new RequestAllowEvent(false, request); + NS_DispatchToMainThread(ev); + return NS_OK; + } + + if (!mOwner && !nsContentUtils::IsCallerChrome()) { + return NS_ERROR_FAILURE; + } + + if (sGeoInitPending) { + mPendingRequests.AppendElement(request); + return NS_OK; + } + + return WatchPositionReady(request); +} + +nsresult +Geolocation::WatchPositionReady(nsGeolocationRequest* aRequest) +{ + if (mOwner) { + if (!RegisterRequestWithPrompt(aRequest)) + return NS_ERROR_NOT_AVAILABLE; + + return NS_OK; + } + + if (!nsContentUtils::IsCallerChrome()) { + return NS_ERROR_FAILURE; + } + + aRequest->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; +} + +void +Geolocation::ServiceReady() +{ + for (uint32_t length = mPendingRequests.Length(); length > 0; --length) { + if (mPendingRequests[0]->IsWatch()) { + WatchPositionReady(mPendingRequests[0]); + } else { + GetCurrentPositionReady(mPendingRequests[0]); + } + + mPendingRequests.RemoveElementAt(0); + } +} + +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<nsPIDOMWindow> window = do_QueryReferent(mOwner); + + if (window) { + bool closed = false; + window->GetClosed(&closed); + if (closed) { + return false; + } + + nsPIDOMWindow* outer = window->GetOuterWindow(); + if (!outer || outer->GetCurrentInnerWindow() != window) { + 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) +{ + return GeolocationBinding::Wrap(aCtx, this); +} diff --git a/dom/geolocation/nsGeolocation.h b/dom/geolocation/nsGeolocation.h new file mode 100644 index 000000000..d71d48b25 --- /dev/null +++ b/dom/geolocation/nsGeolocation.h @@ -0,0 +1,258 @@ +/* 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/. */ + +#ifndef nsGeoLocation_h +#define nsGeoLocation_h + +// Microsoft's API Name hackery sucks +#undef CreateEvent + +#include "mozilla/StaticPtr.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsTArray.h" +#include "nsITimer.h" +#include "nsIObserver.h" +#include "nsWrapperCache.h" + +#include "nsWeakPtr.h" +#include "nsCycleCollectionParticipant.h" + +#include "nsGeoPosition.h" +#include "nsIDOMGeoGeolocation.h" +#include "nsIDOMGeoPosition.h" +#include "nsIDOMGeoPositionError.h" +#include "nsIDOMGeoPositionCallback.h" +#include "nsIDOMGeoPositionErrorCallback.h" +#include "mozilla/dom/GeolocationBinding.h" +#include "mozilla/dom/PositionErrorBinding.h" +#include "mozilla/dom/CallbackObject.h" + +#include "nsIGeolocationProvider.h" +#include "nsIContentPermissionPrompt.h" +#include "nsIDOMWindow.h" +#include "mozilla/Attributes.h" + +class nsGeolocationService; +class nsGeolocationRequest; + +namespace mozilla { +namespace dom { +class Geolocation; +typedef CallbackObjectHolder<PositionCallback, nsIDOMGeoPositionCallback> GeoPositionCallback; +typedef CallbackObjectHolder<PositionErrorCallback, nsIDOMGeoPositionErrorCallback> GeoPositionErrorCallback; +} +} + +struct CachedPositionAndAccuracy { + nsCOMPtr<nsIDOMGeoPosition> position; + bool isHighAccuracy; +}; + +/** + * Singleton that manages the geolocation provider + */ +class nsGeolocationService final : public nsIGeolocationUpdate, public nsIObserver +{ +public: + + static already_AddRefed<nsGeolocationService> GetGeolocationService(); + static mozilla::StaticRefPtr<nsGeolocationService> sService; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIGEOLOCATIONUPDATE + NS_DECL_NSIOBSERVER + + nsGeolocationService() { + mHigherAccuracy = false; + } + + nsresult Init(); + + void HandleMozsettingChanged(nsISupports* aSubject); + void HandleMozsettingValue(const bool aValue); + + // Management of the Geolocation objects + void AddLocator(mozilla::dom::Geolocation* locator); + void RemoveLocator(mozilla::dom::Geolocation* locator); + + void SetCachedPosition(nsIDOMGeoPosition* aPosition); + CachedPositionAndAccuracy GetCachedPosition(); + + // Find and startup a geolocation device (gps, nmea, etc.) + nsresult StartDevice(nsIPrincipal* aPrincipal); + + // Stop the started geolocation device (gps, nmea, etc.) + void StopDevice(); + + // create, or reinitalize the callback timer + void SetDisconnectTimer(); + + // Update the accuracy and notify the provider if changed + void UpdateAccuracy(bool aForceHigh = false); + bool HighAccuracyRequested(); + +private: + + ~nsGeolocationService(); + + // Disconnect timer. When this timer expires, it clears all pending callbacks + // and closes down the provider, unless we are watching a point, and in that + // case, we disable the disconnect timer. + nsCOMPtr<nsITimer> mDisconnectTimer; + + // The object providing geo location information to us. + nsCOMPtr<nsIGeolocationProvider> mProvider; + + // mGeolocators are not owned here. Their constructor + // adds them to this list, and their destructor removes + // them from this list. + nsTArray<mozilla::dom::Geolocation*> mGeolocators; + + // This is the last geo position that we have seen. + CachedPositionAndAccuracy mLastPosition; + + // Current state of requests for higher accuracy + bool mHigherAccuracy; +}; + +namespace mozilla { +namespace dom { + +/** + * Can return a geolocation info + */ +class Geolocation final : public nsIDOMGeoGeolocation, + public nsIGeolocationUpdate, + public nsWrapperCache +{ +public: + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Geolocation, nsIDOMGeoGeolocation) + + NS_DECL_NSIGEOLOCATIONUPDATE + NS_DECL_NSIDOMGEOGEOLOCATION + + Geolocation(); + + nsresult Init(nsIDOMWindow* contentDom=nullptr); + + nsIDOMWindow* GetParentObject() const; + virtual JSObject* WrapObject(JSContext *aCtx) override; + + int32_t WatchPosition(PositionCallback& aCallback, PositionErrorCallback* aErrorCallback, const PositionOptions& aOptions, ErrorResult& aRv); + void GetCurrentPosition(PositionCallback& aCallback, PositionErrorCallback* aErrorCallback, const PositionOptions& aOptions, ErrorResult& aRv); + + // Returns true if any of the callbacks are repeating + bool HasActiveCallbacks(); + + // Register an allowed request + void NotifyAllowedRequest(nsGeolocationRequest* aRequest); + + // Remove request from all callbacks arrays + void RemoveRequest(nsGeolocationRequest* request); + + // Check if there is already ClearWatch called for current + // request & clear if yes + bool ClearPendingRequest(nsGeolocationRequest* aRequest); + + // Shutting down. + void Shutdown(); + + // Getter for the principal that this Geolocation was loaded from + nsIPrincipal* GetPrincipal() { return mPrincipal; } + + // Getter for the window that this Geolocation is owned by + nsIWeakReference* GetOwner() { return mOwner; } + + // Check to see if the widnow still exists + bool WindowOwnerStillExists(); + + // Check to see if any active request requires high accuracy + bool HighAccuracyRequested(); + + // Notification from the service: + void ServiceReady(); + +private: + + ~Geolocation(); + + nsresult GetCurrentPosition(GeoPositionCallback& aCallback, GeoPositionErrorCallback& aErrorCallback, PositionOptions* aOptions); + nsresult WatchPosition(GeoPositionCallback& aCallback, GeoPositionErrorCallback& aErrorCallback, PositionOptions* aOptions, int32_t* aRv); + + bool RegisterRequestWithPrompt(nsGeolocationRequest* request); + + // Methods for the service when it's ready to process requests: + nsresult GetCurrentPositionReady(nsGeolocationRequest* aRequest); + nsresult WatchPositionReady(nsGeolocationRequest* aRequest); + + // Check if clearWatch is already called + bool IsAlreadyCleared(nsGeolocationRequest* aRequest); + + // Two callback arrays. The first |mPendingCallbacks| holds objects for only + // one callback and then they are released/removed from the array. The second + // |mWatchingCallbacks| holds objects until the object is explictly removed or + // there is a page change. All requests held by either array are active, that + // is, they have been allowed and expect to be fulfilled. + + nsTArray<nsRefPtr<nsGeolocationRequest> > mPendingCallbacks; + nsTArray<nsRefPtr<nsGeolocationRequest> > mWatchingCallbacks; + + // window that this was created for. Weak reference. + nsWeakPtr mOwner; + + // where the content was loaded from + nsCOMPtr<nsIPrincipal> mPrincipal; + + // owning back pointer. + nsRefPtr<nsGeolocationService> mService; + + // Watch ID + uint32_t mLastWatchId; + + // Pending requests are used when the service is not ready + nsTArray<nsRefPtr<nsGeolocationRequest> > mPendingRequests; + + // Array containing already cleared watch IDs + nsTArray<int32_t> mClearedWatchIDs; +}; + +class PositionError final : public nsIDOMGeoPositionError, + public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PositionError) + + NS_DECL_NSIDOMGEOPOSITIONERROR + + PositionError(Geolocation* aParent, int16_t aCode); + + Geolocation* GetParentObject() const; + + virtual JSObject* WrapObject(JSContext* aCx) override; + + int16_t Code() const { + return mCode; + } + + void NotifyCallback(const GeoPositionErrorCallback& callback); +private: + ~PositionError(); + int16_t mCode; + nsRefPtr<Geolocation> mParent; +}; + +} + +inline nsISupports* +ToSupports(dom::Geolocation* aGeolocation) +{ + return ToSupports(static_cast<nsIDOMGeoGeolocation*>(aGeolocation)); +} +} + +#endif /* nsGeoLocation_h */ diff --git a/dom/geolocation/nsGeolocationSettings.cpp b/dom/geolocation/nsGeolocationSettings.cpp new file mode 100644 index 000000000..c186c3e4e --- /dev/null +++ b/dom/geolocation/nsGeolocationSettings.cpp @@ -0,0 +1,464 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsISettingsService.h" + +#include "nsGeolocation.h" +#include "nsGeolocationSettings.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/dom/PermissionMessageUtils.h" +#include "mozilla/dom/SettingChangeNotificationBinding.h" + +#include "nsJSUtils.h" +#include "prdtoa.h" + +#define GEO_ALA_TYPE_VALUE_PRECISE "precise" +#define GEO_ALA_TYPE_VALUE_APPROX "blur" +#define GEO_ALA_TYPE_VALUE_FIXED "user-defined" +#define GEO_ALA_TYPE_VALUE_NONE "no-location" + +using mozilla::unused; +using namespace mozilla; +using namespace mozilla::dom; + +NS_IMPL_ISUPPORTS(nsGeolocationSettings, nsIObserver) + +StaticRefPtr<nsGeolocationSettings> nsGeolocationSettings::sSettings; + +already_AddRefed<nsGeolocationSettings> +nsGeolocationSettings::GetGeolocationSettings() +{ + // this singleton is only needed in the parent process... + if (XRE_GetProcessType() == GoannaProcessType_Content) { + return nullptr; + } + + nsRefPtr<nsGeolocationSettings> result; + if (nsGeolocationSettings::sSettings) { + result = nsGeolocationSettings::sSettings; + return result.forget(); + } + + result = new nsGeolocationSettings(); + if (NS_FAILED(result->Init())) { + return nullptr; + } + ClearOnShutdown(&nsGeolocationSettings::sSettings); + nsGeolocationSettings::sSettings = result; + return result.forget(); +} + +nsresult nsGeolocationSettings::Init() +{ + // query for the current settings... + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (!obs) { + return NS_ERROR_FAILURE; + } + + // hook up observers + obs->AddObserver(this, "quit-application", false); + obs->AddObserver(this, "mozsettings-changed", false); + return NS_OK; +} + +NS_IMETHODIMP +nsGeolocationSettings::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + // remove observers + if (!strcmp("quit-application", aTopic)) { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "quit-application"); + obs->RemoveObserver(this, "mozsettings-changed"); + } + return NS_OK; + } + + if (!strcmp("mozsettings-changed", aTopic)) { + HandleMozsettingsChanged(aSubject); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + + +GeolocationSetting +nsGeolocationSettings::LookupGeolocationSetting(int32_t aWatchID) +{ + nsCString *origin; + if (!mCurrentWatches.Get(aWatchID, &origin) || !origin) { + return mGlobalSetting; + } + + // if there is no per-app setting for the given origin, this will + // set gb == nullptr + GeolocationSetting const * const gb = + mPerOriginSettings.Get(NS_ConvertUTF8toUTF16(*origin)); + + // return a copy of the per-app or global settings + return gb ? *gb : mGlobalSetting; +} + + +void +nsGeolocationSettings::HandleGeolocationSettingsChange(const nsAString& aKey, + const JS::Value& aVal) +{ + if (aKey.EqualsASCII(GEO_ALA_ENABLED)) { + HandleGeolocationAlaEnabledChange(aVal); + } else if (aKey.EqualsASCII(GEO_ALA_TYPE)) { + mGlobalSetting.HandleTypeChange(aVal); +#ifdef MOZ_APPROX_LOCATION + } else if (aKey.EqualsASCII(GEO_ALA_APPROX_DISTANCE)) { + mGlobalSetting.HandleApproxDistanceChange(aVal); +#endif + } else if (aKey.EqualsASCII(GEO_ALA_FIXED_COORDS)) { + mGlobalSetting.HandleFixedCoordsChange(aVal); + } else if (aKey.EqualsASCII(GEO_ALA_APP_SETTINGS)) { + HandleGeolocationPerOriginSettingsChange(aVal); + } else if (aKey.EqualsASCII(GEO_ALA_ALWAYS_PRECISE)) { + HandleGeolocationAlwaysPreciseChange(aVal); + } +} + +void +nsGeolocationSettings::HandleMozsettingsChanged(nsISupports* aSubject) +{ + MOZ_ASSERT(NS_IsMainThread()); + + RootedDictionary<SettingChangeNotification> setting(nsContentUtils::RootingCx()); + if (!WrappedJSToDictionary(aSubject, setting)) { + return; + } + + GPSLOG("mozsettings changed: %s", NS_ConvertUTF16toUTF8(setting.mKey).get()); + + // and handle the geolocation settings change... + HandleGeolocationSettingsChange(setting.mKey, setting.mValue); +} + + +void +nsGeolocationSettings::HandleGeolocationSettingsError(const nsAString& aName) +{ + if (aName.EqualsASCII(GEO_ALA_ENABLED)) { + GPSLOG("Unable to get value for '" GEO_ALA_ENABLED "'"); + } else if (aName.EqualsASCII(GEO_ALA_TYPE)) { + GPSLOG("Unable to get value for '" GEO_ALA_TYPE "'"); +#ifdef MOZ_APPROX_LOCATION + } else if (aName.EqualsASCII(GEO_ALA_APPROX_DISTANCE)) { + GPSLOG("Unable to get value for '" GEO_ALA_APPROX_DISTANCE "'"); +#endif + } else if (aName.EqualsASCII(GEO_ALA_FIXED_COORDS)) { + GPSLOG("Unable to get value for '" GEO_ALA_FIXED_COORDS "'"); + } else if (aName.EqualsASCII(GEO_ALA_APP_SETTINGS)) { + GPSLOG("Unable to get value for '" GEO_ALA_APP_SETTINGS "'"); + } else if (aName.EqualsASCII(GEO_ALA_ALWAYS_PRECISE)) { + GPSLOG("Unable to get value for '" GEO_ALA_ALWAYS_PRECISE "'"); + } +} + +void +nsGeolocationSettings::PutWatchOrigin(int32_t aWatchID, + const nsCString& aOrigin) +{ + if (aWatchID < 0) { + return; + } + + GPSLOG("mapping watch ID %d to origin %s", aWatchID, aOrigin.get()); + mCurrentWatches.Put(static_cast<uint32_t>(aWatchID), new nsCString(aOrigin)); +} + +void +nsGeolocationSettings::RemoveWatchOrigin(int32_t aWatchID) +{ + GPSLOG("unmapping watch ID %d", aWatchID); + mCurrentWatches.Remove(static_cast<uint32_t>(aWatchID)); +} + +void +nsGeolocationSettings::GetWatchOrigin(int32_t aWatchID, nsCString& aOrigin) +{ + nsCString *str; + if (!mCurrentWatches.Get(aWatchID, &str) || !str) { + return; + } + aOrigin = *str; + GPSLOG("returning origin %s for watch ID %d", aOrigin.get(), aWatchID); +} + +void +nsGeolocationSettings::HandleGeolocationAlaEnabledChange(const JS::Value& aVal) +{ + if (!aVal.isBoolean()) { + return; + } + + mAlaEnabled = aVal.toBoolean(); +} + +void +nsGeolocationSettings::HandleGeolocationPerOriginSettingsChange(const JS::Value& aVal) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!aVal.isObject()) { + return; + } + + // clear the hash table + mPerOriginSettings.Clear(); + + // root the object and get the global + JS::Rooted<JSObject*> obj(nsContentUtils::RootingCx(), &aVal.toObject()); + MOZ_ASSERT(obj); + nsIGlobalObject* global = xpc::NativeGlobal(obj); + NS_ENSURE_TRUE_VOID(global && global->GetGlobalJSObject()); + + // because the spec requires calling getters when enumerating the key of a + // dictionary + AutoEntryScript aes(global); + aes.TakeOwnershipOfErrorReporting(); + JSContext *cx = aes.cx(); + JS::AutoIdArray ids(cx, JS_Enumerate(cx, obj)); + + // if we get no ids then the exception list is empty and we can return here. + if (!ids) { + return; + } + + // go through all of the objects in the exceptions dictionary + for (size_t i = 0; i < ids.length(); i++) { + JS::RootedId id(cx); + id = ids[i]; + + // if it is an app that is always precise, skip it + nsAutoJSString origin; + if (!origin.init(cx, id)) { + continue; + } + if (mAlwaysPreciseApps.Contains(origin)) { + continue; + } + + // get the app setting object + JS::RootedValue propertyValue(cx); + if (!JS_GetPropertyById(cx, obj, id, &propertyValue) || !propertyValue.isObject()) { + continue; + } + JS::RootedObject settingObj(cx, &propertyValue.toObject()); + + GeolocationSetting *settings = new GeolocationSetting(origin); + GPSLOG("adding exception for %s", NS_ConvertUTF16toUTF8(origin).get()); + + // get the geolocation type + JS::RootedValue fm(cx); + if (JS_GetProperty(cx, settingObj, "type", &fm)) { + settings->HandleTypeChange(fm); + } + +#ifdef MOZ_APPROX_LOCATION + // get the approximate distance if there is one + JS::RootedValue distance(cx); + if (JS_GetProperty(cx, settingObj, "distance", &distance)) { + settings->HandleApproxDistanceChange(distance); + } +#endif + + // get and parse the coords, if any + JS::RootedValue coords(cx); + if (JS_GetProperty(cx, settingObj, "coords", &coords)) { + settings->HandleFixedCoordsChange(coords); + } + + // add the per-app setting object to the hashtable + mPerOriginSettings.Put(origin, settings); + } +} + +void +nsGeolocationSettings::HandleGeolocationAlwaysPreciseChange(const JS::Value& aVal) +{ + if (!aVal.isObject()) { + return; + } + + // clear the list of apps that are always precise + mAlwaysPreciseApps.Clear(); + + // root the object and get the global + JS::Rooted<JSObject*> obj(nsContentUtils::RootingCx(), &aVal.toObject()); + MOZ_ASSERT(obj); + nsIGlobalObject* global = xpc::NativeGlobal(obj); + NS_ENSURE_TRUE_VOID(global && global->GetGlobalJSObject()); + + // the spec requires calling getters when accessing array by index + AutoEntryScript aes(global); + aes.TakeOwnershipOfErrorReporting(); + JSContext *cx = aes.cx(); + + if (!JS_IsArrayObject(cx, obj)) { + return; + } + + uint32_t length; + if (!JS_GetArrayLength(cx, obj, &length)) { + return; + } + + // process the list of apps... + for (uint32_t i = 0; i < length; ++i) { + JS::RootedValue value(cx); + + if (!JS_GetElement(cx, obj, i, &value) || !value.isString()) { + continue; + } + + nsAutoJSString origin; + if (!origin.init(cx, value)) { + continue; + } + + GPSLOG("adding always precise for %s", NS_ConvertUTF16toUTF8(origin).get()); + + // add the origin to the list of apps that will always receive + // precise location information + mAlwaysPreciseApps.AppendElement(origin); + } +} + + +void +GeolocationSetting::HandleTypeChange(const JS::Value& aVal) +{ + nsAutoJSString str; + if (!str.init(aVal)) { + return; + } + + GeolocationFuzzMethod fm = GEO_ALA_TYPE_DEFAULT; + if (str.EqualsASCII(GEO_ALA_TYPE_VALUE_PRECISE)) { + fm = GEO_ALA_TYPE_PRECISE; + } else if (str.EqualsASCII(GEO_ALA_TYPE_VALUE_APPROX)) { +#ifdef MOZ_APPROX_LOCATION + fm = GEO_ALA_TYPE_APPROX; +#else + // we are loading a profile from a build with MOZ_APPROX_LOCATION + // enabled, then we need to force the type to a sane value + fm = GEO_ALA_TYPE_NONE; +#endif + } else if (str.EqualsASCII(GEO_ALA_TYPE_VALUE_FIXED)) { + fm = GEO_ALA_TYPE_FIXED; + } else if (str.EqualsASCII(GEO_ALA_TYPE_VALUE_NONE)) { + fm = GEO_ALA_TYPE_NONE; + } + + if ((fm >= GEO_ALA_TYPE_FIRST) && (fm <= GEO_ALA_TYPE_LAST)) { + GPSLOG("changing type for %s to %s (%d)", + (mOrigin.IsEmpty() ? "global" : NS_ConvertUTF16toUTF8(mOrigin).get()), + NS_ConvertUTF16toUTF8(str).get(), + static_cast<int>(fm)); + mFuzzMethod = fm; + } + + // based on the new type, we need to clean up the other settings + switch (mFuzzMethod) { + case GEO_ALA_TYPE_PRECISE: + case GEO_ALA_TYPE_NONE: +#ifdef MOZ_APPROX_LOCATION + mDistance = 0; +#endif + mLatitude = 0.0; + mLongitude = 0.0; + break; +#ifdef MOZ_APPROX_LOCATION + case GEO_ALA_TYPE_APPROX: + mLatitude = 0.0; + mLongitude = 0.0; + break; +#endif + case GEO_ALA_TYPE_FIXED: +#ifdef MOZ_APPROX_LOCATION + mDistance = 0; +#endif + break; + } +} + +#ifdef MOZ_APPROX_LOCATION +void +GeolocationSetting::HandleApproxDistanceChange(const JS::Value& aVal) +{ + if (!aVal.isInt32()) { + return; + } + + GPSLOG("changing approx distance for %s to %d", + (mOrigin.IsEmpty() ? "global" : NS_ConvertUTF16toUTF8(mOrigin).get()), + aVal.toInt32()); + + // set the approximate distance + mDistance = aVal.toInt32(); +} +#endif + + +void +GeolocationSetting::HandleFixedCoordsChange(const JS::Value& aVal) +{ + nsAutoJSString str; + if (!str.init(aVal)) { + return; + } + + // parse the string and store the global lat/lon + // the string is in the form of @1.2345,6.7890 + // check for leading '@' and a comma in the middle + int32_t const comma = str.Find(","); + if ( (str.CharAt(0) != '@') || (comma == -1) ) { + return; + } + + // pull the lat and lon out of the string and convert to doubles + nsresult rv; + nsString slat(Substring(str, 1, comma - 1)); + nsString slon(Substring(str, comma + 1)); + double lat = slat.ToDouble(&rv); + NS_ENSURE_SUCCESS(rv,); + double lon = slon.ToDouble(&rv); + NS_ENSURE_SUCCESS(rv,); + + // store the values + mLatitude = lat; + mLongitude = lon; + + GPSLOG("changing coords for %s to %s (%f, %f)", + (mOrigin.IsEmpty() ? "global" : NS_ConvertUTF16toUTF8(mOrigin).get()), + NS_ConvertUTF16toUTF8(str).get(), + mLatitude, mLongitude); +} + diff --git a/dom/geolocation/nsGeolocationSettings.h b/dom/geolocation/nsGeolocationSettings.h new file mode 100644 index 000000000..a5b112947 --- /dev/null +++ b/dom/geolocation/nsGeolocationSettings.h @@ -0,0 +1,165 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsGeolocationSettings_h +#define nsGeolocationSettings_h + +#include "mozilla/Attributes.h" +#include "mozilla/StaticPtr.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsClassHashtable.h" +#include "nsString.h" +#include "nsIObserver.h" +#include "nsJSUtils.h" +#include "nsTArray.h" + +#if (defined(MOZ_GPS_DEBUG) && defined(ANDROID)) +#include <android/log.h> +#define GPSLOG(fmt, ...) __android_log_print(ANDROID_LOG_WARN, "GPS", "%12s:%-5d " fmt, __FILE__, __LINE__, ##__VA_ARGS__) +#else +#define GPSLOG(...) {;} +#endif // MOZ_GPS_DEBUG && ANDROID + +// The settings key. +#define GEO_ENABLED "geolocation.enabled" +#define GEO_ALA_ENABLED "ala.settings.enabled" +#define GEO_ALA_TYPE "geolocation.type" +#define GEO_ALA_FIXED_COORDS "geolocation.fixed_coords" +#define GEO_ALA_APP_SETTINGS "geolocation.app_settings" +#define GEO_ALA_ALWAYS_PRECISE "geolocation.always_precise" +#ifdef MOZ_APPROX_LOCATION +#define GEO_ALA_APPROX_DISTANCE "geolocation.approx_distance" +#endif + +enum GeolocationFuzzMethod { + GEO_ALA_TYPE_PRECISE, // default, GPS/AGPS location + GEO_ALA_TYPE_FIXED, // user supplied lat/long + GEO_ALA_TYPE_NONE, // no location given +#ifdef MOZ_APPROX_LOCATION + GEO_ALA_TYPE_APPROX // approximate, grid-based location +#endif +}; + +#define GEO_ALA_TYPE_DEFAULT (GEO_ALA_TYPE_PRECISE) +#define GEO_ALA_TYPE_FIRST (GEO_ALA_TYPE_PRECISE) +#ifdef MOZ_APPROX_LOCATION +#define GEO_ALA_TYPE_LAST (GEO_ALA_TYPE_APPROX) +#else +#define GEO_ALA_TYPE_LAST (GEO_ALA_TYPE_NONE) +#endif + +/** + * Simple class for holding the geolocation settings values. + */ + +class GeolocationSetting final { +public: + explicit GeolocationSetting(const nsString& aOrigin) : + mFuzzMethod(GEO_ALA_TYPE_DEFAULT), +#ifdef MOZ_APPROX_LOCATION + mDistance(0), +#endif + mLatitude(0.0), + mLongitude(0.0), + mOrigin(aOrigin) {} + + GeolocationSetting(const GeolocationSetting& rhs) : + mFuzzMethod(rhs.mFuzzMethod), +#ifdef MOZ_APPROX_LOCATION + mDistance(rhs.mDistance), +#endif + mLatitude(rhs.mLatitude), + mLongitude(rhs.mLongitude), + mOrigin(rhs.mOrigin) {} + + ~GeolocationSetting() {} + + GeolocationSetting& operator=(const GeolocationSetting& rhs) { + mFuzzMethod = rhs.mFuzzMethod; +#ifdef MOZ_APPROX_LOCATION + mDistance = rhs.mDistance; +#endif + mLatitude = rhs.mLatitude; + mLongitude = rhs.mLongitude; + mOrigin = rhs.mOrigin; + return *this; + } + + void HandleTypeChange(const JS::Value& aVal); + void HandleApproxDistanceChange(const JS::Value& aVal); + void HandleFixedCoordsChange(const JS::Value& aVal); + + inline GeolocationFuzzMethod GetType() const { return mFuzzMethod; } +#ifdef MOZ_APPROX_LOCATION + inline int32_t GetApproxDistance() const { return mDistance; } +#endif + inline double GetFixedLatitude() const { return mLatitude; } + inline double GetFixedLongitude() const { return mLongitude; } + inline const nsString& GetOrigin() const { return mOrigin; } + +private: + GeolocationSetting() {} // can't default construct + GeolocationFuzzMethod mFuzzMethod; +#ifdef MOZ_APPROX_LOCATION + int32_t mDistance; +#endif + double mLatitude, + mLongitude; + nsString mOrigin; +}; + +/** + * Singleton that holds the global and per-origin geolocation settings. + */ +class nsGeolocationSettings final : public nsIObserver +{ +public: + static already_AddRefed<nsGeolocationSettings> GetGeolocationSettings(); + static mozilla::StaticRefPtr<nsGeolocationSettings> sSettings; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + + nsGeolocationSettings() : mAlaEnabled(false), mGlobalSetting(NullString()) {} + nsresult Init(); + + void HandleGeolocationSettingsChange(const nsAString& aKey, const JS::Value& aVal); + void HandleGeolocationSettingsError(const nsAString& aName); + + void PutWatchOrigin(int32_t aWatchID, const nsCString& aOrigin); + void RemoveWatchOrigin(int32_t aWatchID); + void GetWatchOrigin(int32_t aWatchID, nsCString& aOrigin); + inline bool IsAlaEnabled() const { return mAlaEnabled; } + + // given a watch ID, retrieve the geolocation settings. the watch ID is + // mapped to the origin of the listener/request which is then used to + // retreive the geolocation settings for the origin. + // if the origin is in the always-precise list, the settings will always be + // 'precise'. if the origin has origin-specific settings, that will be returned + // otherwise the global geolocation settings will be returned. + // NOTE: this returns a copy of the settings to enforce read-only client access + GeolocationSetting LookupGeolocationSetting(int32_t aWatchID); + +private: + ~nsGeolocationSettings() {} + nsGeolocationSettings(const nsGeolocationSettings&) : + mGlobalSetting(NullString()) {} // can't copy obj + + void HandleMozsettingsChanged(nsISupports* aSubject); + void HandleGeolocationAlaEnabledChange(const JS::Value& aVal); + void HandleGeolocationPerOriginSettingsChange(const JS::Value& aVal); + void HandleGeolocationAlwaysPreciseChange(const JS::Value& aVal); + +private: + bool mAlaEnabled; + GeolocationSetting mGlobalSetting; + nsClassHashtable<nsStringHashKey, GeolocationSetting> mPerOriginSettings; + nsTArray<nsString> mAlwaysPreciseApps; + nsClassHashtable<nsUint32HashKey, nsCString> mCurrentWatches; +}; + +#endif /* nsGeolocationSettings_h */ + |