Bug 1073419 - [ALA] Adjustable location accuracy. r=jdm, r=mt
☠☠ backed out by c0e72ece8901 ☠ ☠
authorDave Huseby <dhuseby@mozilla.com>
Fri, 24 Oct 2014 18:42:00 -0400
changeset 212466 9742b31c634a90d3acd310ea6bb2d388ba9f7b47
parent 212465 fd669321cd9dfa9a124ebbfe81048ec632f19296
child 212467 dddf0da8eb3fceddf57a3104e670adfe02d6d139
push id11671
push userryanvm@gmail.com
push dateMon, 27 Oct 2014 15:17:44 +0000
treeherderb2g-inbound@9742b31c634a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm, mt
bugs1073419
milestone36.0a1
Bug 1073419 - [ALA] Adjustable location accuracy. r=jdm, r=mt
configure.in
dom/geolocation/moz.build
dom/geolocation/nsGeoGridFuzzer.cpp
dom/geolocation/nsGeoGridFuzzer.h
dom/geolocation/nsGeolocation.cpp
dom/geolocation/nsGeolocationSettings.cpp
dom/geolocation/nsGeolocationSettings.h
dom/ipc/ContentParent.cpp
--- a/configure.in
+++ b/configure.in
@@ -7003,16 +7003,41 @@ MOZ_ARG_ENABLE_BOOL(logrefcnt,
     _ENABLE_LOGREFCNT= )
 if test "$_ENABLE_LOGREFCNT" = "1"; then
     AC_DEFINE(FORCE_BUILD_REFCNT_LOGGING)
 elif test -z "$_ENABLE_LOGREFCNT"; then
     AC_DEFINE(NO_BUILD_REFCNT_LOGGING)
 fi
 
 dnl ========================================================
+dnl moz_aproximate_location
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(approximate_location,
+[ --enable-approximate-location    Enable approximate location ],
+    MOZ_APPROX_LOCATION=1,
+    MOZ_APPROX_LOCATION= )
+if test -n "$MOZ_APPROX_LOCATION"; then
+    AC_DEFINE(MOZ_APPROX_LOCATION)
+fi
+
+dnl ========================================================
+dnl moz_gps_debug
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(gps_debug,
+[ --enable-gps-debug    Enable gps specific debug messages ],
+    MOZ_GPS_DEBUG=1,
+    MOZ_GPS_DEBUG= )
+if test -n "$MOZ_GPS_DEBUG"; then
+    AC_DEFINE(MOZ_GPS_DEBUG)
+fi
+if test -n "$MOZ_DEBUG"; then
+    AC_DEFINE(MOZ_GPS_DEBUG)
+fi
+
+dnl ========================================================
 dnl moz_dump_painting
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(dump-painting,
 [  --enable-dump-painting          Enable paint debugging.],
     MOZ_DUMP_PAINTING=1,
     MOZ_DUMP_PAINTING= )
 if test -n "$MOZ_DUMP_PAINTING"; then
     AC_DEFINE(MOZ_DUMP_PAINTING)
--- a/dom/geolocation/moz.build
+++ b/dom/geolocation/moz.build
@@ -1,21 +1,24 @@
 # -*- 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',
 ]
 
 UNIFIED_SOURCES += [
+    'nsGeoGridFuzzer.cpp',
     'nsGeolocation.cpp',
+    'nsGeolocationSettings.cpp',
     'nsGeoPosition.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
new file mode 100644
--- /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
new file mode 100644
--- /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 MOZ_FINAL
+{
+public:
+
+  static already_AddRefed<nsIDOMGeoPosition>
+    FuzzLocation(const GeolocationSetting& aSetting, nsIDOMGeoPosition* aPosition);
+
+private:
+  nsGeoGridFuzzer() {} // can't construct
+  nsGeoGridFuzzer(const nsGeoGridFuzzer&) {} // can't copy
+};
+
+#endif
--- a/dom/geolocation/nsGeolocation.cpp
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -5,32 +5,37 @@
 #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"
@@ -43,18 +48,18 @@ class nsIPrincipal;
 #ifdef MOZ_WIDGET_COCOA
 #include "CoreLocationLocationProvider.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 settings key.
-#define GEO_SETINGS_ENABLED          "geolocation.enabled"
+// the geolocation enabled setting
+#define GEO_SETTINGS_ENABLED          "geolocation.enabled"
 
 using mozilla::unused;          // <snicker>
 using namespace mozilla;
 using namespace mozilla::dom;
 
 class nsGeolocationRequest MOZ_FINAL
  : public nsIContentPermissionRequest
  , public nsITimerCallback
@@ -71,28 +76,30 @@ class nsGeolocationRequest MOZ_FINAL
   nsGeolocationRequest(Geolocation* aLocator,
                        const GeoPositionCallback& aCallback,
                        const GeoPositionErrorCallback& aErrorCallback,
                        PositionOptions* aOptions,
                        bool aWatchPositionRequest = false,
                        int32_t aWatchId = 0);
   void Shutdown();
 
-  void SendLocation(nsIDOMGeoPosition* location);
+  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;
@@ -125,32 +132,52 @@ public:
   GeolocationSettingsCallback() {
     MOZ_COUNT_CTOR(GeolocationSettingsCallback);
   }
 
   NS_IMETHOD Handle(const nsAString& aName, JS::Handle<JS::Value> aResult)
   {
     MOZ_ASSERT(NS_IsMainThread());
 
-    // The geolocation is enabled by default:
-    bool value = true;
-    if (aResult.isBoolean()) {
-      value = aResult.toBoolean();
+    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);
+      }
     }
 
-    MozSettingValue(value);
     return NS_OK;
   }
 
   NS_IMETHOD HandleError(const nsAString& aName)
   {
-    NS_WARNING("Unable to get value for '" GEO_SETINGS_ENABLED "'");
+    if (aName.EqualsASCII(GEO_SETTINGS_ENABLED)) {
+      GPSLOG("Unable to get value for '" GEO_SETTINGS_ENABLED "'");
 
-    // Default it's enabled:
-    MozSettingValue(true);
+      // 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);
@@ -483,16 +510,75 @@ 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() == GeckoProcessType_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;
   }
 
@@ -508,16 +594,22 @@ nsGeolocationRequest::SendLocation(nsIDO
   }
 
   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;
   }
@@ -554,17 +646,18 @@ nsGeolocationRequest::GetPrincipal()
     return nullptr;
   }
   return mLocator->GetPrincipal();
 }
 
 NS_IMETHODIMP
 nsGeolocationRequest::Update(nsIDOMGeoPosition* aPosition)
 {
-  nsCOMPtr<nsIRunnable> ev = new RequestSendLocationEvent(aPosition, this);
+  nsCOMPtr<nsIDOMGeoPosition> pos = AdjustedLocation(aPosition);
+  nsCOMPtr<nsIRunnable> ev = new RequestSendLocationEvent(pos, this);
   NS_DispatchToMainThread(ev);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsGeolocationRequest::LocationUpdatePending()
 {
   if (!mTimeoutTimer) {
@@ -641,18 +734,41 @@ nsresult nsGeolocationService::Init()
     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_SETINGS_ENABLED, callback);
+    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();
@@ -719,23 +835,27 @@ nsGeolocationService::HandleMozsettingCh
 
     AutoJSAPI jsapi;
     jsapi.Init();
     JSContext* cx = jsapi.cx();
     RootedDictionary<SettingChangeNotification> setting(cx);
     if (!WrappedJSToDictionary(cx, aSubject, setting)) {
       return;
     }
-    if (!setting.mKey.EqualsASCII(GEO_SETINGS_ENABLED)) {
+    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
@@ -1320,16 +1440,17 @@ Geolocation::WatchPosition(GeoPositionCa
   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;
   }
@@ -1442,18 +1563,17 @@ Geolocation::NotifyAllowedRequest(nsGeol
   }
 }
 
 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);
+    nsCOMPtr<nsIRunnable> ev = new RequestAllowEvent(allow, request);
     NS_DispatchToMainThread(ev);
     return true;
   }
 
   nsCOMPtr<nsIRunnable> ev  = new RequestPromptEvent(request, mOwner);
   NS_DispatchToMainThread(ev);
   return true;
 }
new file mode 100644
--- /dev/null
+++ b/dom/geolocation/nsGeolocationSettings.cpp
@@ -0,0 +1,460 @@
+/* -*- 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;
+
+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() == GeckoProcessType_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());
+
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JSContext* cx = jsapi.cx();
+  RootedDictionary<SettingChangeNotification> setting(cx);
+  if (!WrappedJSToDictionary(cx, 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)
+{
+  if (!aVal.isObject()) {
+    return;
+  }
+
+  // clear the hash table
+  mPerOriginSettings.Clear();
+
+  // enumerate the array
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JSContext* cx = jsapi.cx();
+  JS::Rooted<JSObject*> obj(cx, &aVal.toObject());
+  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];
+
+    JS::RootedValue v(cx);
+    if (!JS_IdToValue(cx, id, &v) || !v.isString())
+      continue;
+
+    JS::RootedString str(cx, v.toString());
+    if (!str)
+      continue;
+
+    // get the origin for the app
+    nsString origin;
+    if (!AssignJSString(cx, origin, str))
+      continue;
+
+    // if it is an app that is always precise, skip it
+    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();
+
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JSContext* cx = jsapi.cx();
+  JS::Rooted<JSObject*> obj(cx, &aVal.toObject());
+  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;
+    }
+
+    nsString origin;
+    if (!AssignJSString(cx, origin, value.toString())) {
+      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)
+{
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JSContext* cx = jsapi.cx();
+  nsString str;
+  if (!aVal.isString() || !AssignJSString(cx, str, aVal.toString())) {
+    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)
+{
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JSContext* cx = jsapi.cx();
+  nsString str;
+  if (!aVal.isString() || !AssignJSString(cx, str, aVal.toString()) || str.IsEmpty()) {
+    return;
+  }
+
+  // parse the string and store the global lat/lon
+  // the @ character is present in the GPS coord strings we receive
+  int32_t const comma = str.Find(",");
+  if ( (str.CharAt(0) != '@') || (comma == -1) ) {
+    return;
+  }
+
+  nsresult rv;
+  nsString slat(Substring(str, 1, comma));
+  nsString slon(Substring(str, comma + 1));
+  double lat = slat.ToDouble(&rv);
+  NS_ENSURE_SUCCESS(rv,);
+  double lon = slon.ToDouble(&rv);
+  NS_ENSURE_SUCCESS(rv,);
+  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);
+}
+
new file mode 100644
--- /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 MOZ_FINAL {
+public:
+  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 MOZ_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 */
+
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -79,16 +79,17 @@
 #include "nsCDefaultURIFixup.h"
 #include "nsCExternalHandlerService.h"
 #include "nsCOMPtr.h"
 #include "nsChromeRegistryChrome.h"
 #include "nsConsoleMessage.h"
 #include "nsConsoleService.h"
 #include "nsDebugImpl.h"
 #include "nsFrameMessageManager.h"
+#include "nsGeolocationSettings.h"
 #include "nsHashPropertyBag.h"
 #include "nsIAlertsService.h"
 #include "nsIAppsService.h"
 #include "nsIClipboard.h"
 #include "nsICycleCollectorListener.h"
 #include "nsIDocument.h"
 #include "nsIDOMGeoGeolocation.h"
 #include "mozilla/dom/WakeLock.h"
@@ -3682,41 +3683,72 @@ ContentParent::RecvAddGeolocationListene
         }
     }
 #endif /* MOZ_CHILD_PERMISSIONS */
 
     // To ensure no geolocation updates are skipped, we always force the
     // creation of a new listener.
     RecvRemoveGeolocationListener();
     mGeolocationWatchID = AddGeolocationListener(this, aHighAccuracy);
+
+    // let the the settings cache know the origin of the new listener
+    nsAutoCString origin;
+    // hint to the compiler to use the conversion operator to nsIPrincipal*
+    nsCOMPtr<nsIPrincipal> principal = static_cast<nsIPrincipal*>(aPrincipal);
+    if (!principal) {
+      return true;
+    }
+    principal->GetOrigin(getter_Copies(origin));
+    nsRefPtr<nsGeolocationSettings> gs = nsGeolocationSettings::GetGeolocationSettings();
+    if (gs) {
+      gs->PutWatchOrigin(mGeolocationWatchID, origin);
+    }
     return true;
 }
 
 bool
 ContentParent::RecvRemoveGeolocationListener()
 {
     if (mGeolocationWatchID != -1) {
         nsCOMPtr<nsIDOMGeoGeolocation> geo = do_GetService("@mozilla.org/geolocation;1");
         if (!geo) {
             return true;
         }
         geo->ClearWatch(mGeolocationWatchID);
+
+        nsRefPtr<nsGeolocationSettings> gs = nsGeolocationSettings::GetGeolocationSettings();
+        if (gs) {
+          gs->RemoveWatchOrigin(mGeolocationWatchID);
+        }
         mGeolocationWatchID = -1;
     }
     return true;
 }
 
 bool
 ContentParent::RecvSetGeolocationHigherAccuracy(const bool& aEnable)
 {
     // This should never be called without a listener already present,
     // so this check allows us to forgo securing privileges.
     if (mGeolocationWatchID != -1) {
+        nsCString origin;
+        nsRefPtr<nsGeolocationSettings> gs = nsGeolocationSettings::GetGeolocationSettings();
+        // get the origin stored for the curent watch ID
+        if (gs) {
+          gs->GetWatchOrigin(mGeolocationWatchID, origin);
+        }
+
+        // remove and recreate a new, high-accuracy listener
         RecvRemoveGeolocationListener();
         mGeolocationWatchID = AddGeolocationListener(this, aEnable);
+
+        // map the new watch ID to the origin
+        if (gs) {
+          gs->PutWatchOrigin(mGeolocationWatchID, origin);
+        }
     }
     return true;
 }
 
 NS_IMETHODIMP
 ContentParent::HandleEvent(nsIDOMGeoPosition* postion)
 {
     unused << SendGeolocationUpdate(GeoPosition(postion));