Bug 805965 - Geolocation fix jerks around. r=jdm/bz. a=blocking-basecamp
authorDoug Turner <dougt@dougt.org>
Tue, 06 Nov 2012 12:34:03 -0800
changeset 116772 38bff1f270d015c391a0b245d2c79145ffda78d7
parent 116771 21922e8da10f03aa6cfdd7db62b14605576ca060
child 116773 4455e1fa4dad21eac8d8511d6e7efc3bef6b6ec6
push id1708
push userakeybl@mozilla.com
push dateMon, 19 Nov 2012 21:10:21 +0000
treeherdermozilla-beta@27b14fe50103 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm, bz, blocking-basecamp
bugs805965
milestone18.0a2
Bug 805965 - Geolocation fix jerks around. r=jdm/bz. a=blocking-basecamp
dom/src/geolocation/nsGeolocation.cpp
dom/src/geolocation/nsGeolocation.h
--- a/dom/src/geolocation/nsGeolocation.cpp
+++ b/dom/src/geolocation/nsGeolocation.cpp
@@ -267,16 +267,17 @@ nsDOMGeoPositionError::NotifyCallback(ns
 
 nsGeolocationRequest::nsGeolocationRequest(nsGeolocation* aLocator,
                                            nsIDOMGeoPositionCallback* aCallback,
                                            nsIDOMGeoPositionErrorCallback* aErrorCallback,
                                            bool aWatchPositionRequest,
                                            int32_t aWatchId)
   : mAllowed(false),
     mCleared(false),
+    mIsFirstUpdate(true),
     mIsWatchPositionRequest(aWatchPositionRequest),
     mCallback(aCallback),
     mErrorCallback(aErrorCallback),
     mLocator(aLocator),
     mWatchId(aWatchId)
 {
 }
 
@@ -500,26 +501,35 @@ nsGeolocationRequest::SendLocation(nsIDO
   stack->Pop(&cx);
 
   if (mIsWatchPositionRequest) {
     SetTimeoutTimer();
   }
 }
 
 bool
-nsGeolocationRequest::Update(nsIDOMGeoPosition* aPosition)
+nsGeolocationRequest::Update(nsIDOMGeoPosition* aPosition, bool aIsBetter)
 {
   if (!mAllowed) {
     return false;
   }
-
-  nsCOMPtr<nsIRunnable> ev  = new RequestSendLocationEvent(aPosition,
-							   this,
-							   mIsWatchPositionRequest ? nullptr : mLocator);
-  NS_DispatchToMainThread(ev);
+  // Only dispatch callbacks if this is the first position for this request, or
+  // if the accuracy is as good or improving.
+  //
+  // This ensures that all listeners get at least one position callback, particularly
+  // in the case when newly detected positions are all less accurate than the cached one.
+  //
+  // Fixes bug 596481
+  if (mIsFirstUpdate || aIsBetter) {
+    mIsFirstUpdate = PR_FALSE;
+    nsCOMPtr<nsIRunnable> ev  = new RequestSendLocationEvent(aPosition,
+                                                             this,
+                                                             mIsWatchPositionRequest ? nullptr : mLocator);
+    NS_DispatchToMainThread(ev);
+  }
   return true;
 }
 
 void
 nsGeolocationRequest::Shutdown()
 {
   if (mOptions && mOptions->enableHighAccuracy) {
     nsRefPtr<nsGeolocationService> gs = nsGeolocationService::GetGeolocationService();
@@ -784,24 +794,117 @@ nsGeolocationService::Observe(nsISupport
   }
 
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsGeolocationService::Update(nsIDOMGeoPosition *aSomewhere)
 {
-  SetCachedPosition(aSomewhere);
+  // here we have to determine this aSomewhere is a "better"
+  // position than any previously recv'ed.
+
+  bool isBetter = IsBetterPosition(aSomewhere);
+
+  if (isBetter) {
+    SetCachedPosition(aSomewhere);
+  }
 
   for (uint32_t i = 0; i< mGeolocators.Length(); i++) {
-    mGeolocators[i]->Update(aSomewhere);
+    mGeolocators[i]->Update(aSomewhere, isBetter);
   }
   return NS_OK;
 }
 
+PRBool
+nsGeolocationService::IsBetterPosition(nsIDOMGeoPosition *aSomewhere)
+{
+  if (!aSomewhere) {
+    return false;
+  }
+  
+  if (mProviders.Count() == 1 || !mLastPosition) {
+    return true;
+  }
+
+  nsCOMPtr<nsIDOMGeoPositionCoords> coords;
+  mLastPosition->GetCoords(getter_AddRefs(coords));
+  if (!coords) {
+    return false;
+  }
+
+  double oldAccuracy;
+  nsresult rv = coords->GetAccuracy(&oldAccuracy);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  double oldLat, oldLon;
+  rv = coords->GetLongitude(&oldLon);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  rv = coords->GetLatitude(&oldLat);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  aSomewhere->GetCoords(getter_AddRefs(coords));
+  if (!coords) {
+    return false;
+  }
+
+  double newAccuracy;
+  rv = coords->GetAccuracy(&newAccuracy);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  double newLat, newLon;
+  rv = coords->GetLongitude(&newLon);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  rv = coords->GetLatitude(&newLat);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  // Latitude and longitude is reported in degrees.
+  // However, it is easier to work in radian:
+  // see: http://en.wikipedia.org/wiki/Radian
+  double radsInDeg = M_PI / 180.0;
+
+  newLat *= radsInDeg;
+  newLon *= radsInDeg;
+  oldLat *= radsInDeg;
+  oldLon *= radsInDeg;
+
+  // WGS84 equatorial radius of earth = 6378137m
+  // http://en.wikipedia.org/wiki/WGS84
+  double radius = 6378137;
+
+  // We want to calculate the "Great Circle distance"
+  // between the point (lat1, lon1) and (lat2, lon2).  We
+  // will use the spherical law of cosines to the triangle
+  // formed by our two points and the north pole.
+  //
+  // a = sin ( lat1 ) * sin ( lat2 )  + cos ( lat1 ) * cos (lat2) * cos (lon1 - lon2)
+  // R = radius of circle
+  // distance = arccos ( a ) * R 
+  //
+  // http://en.wikipedia.org/wiki/Great-circle_distance
+
+  double delta = acos( (sin(newLat) * sin(oldLat)) +
+                       (cos(newLat) * cos(oldLat) * cos(oldLon - newLon)) ) * radius; 
+
+  // The threshold is when the distance between the two
+  // positions exceeds the worse (larger value) of the two
+  // accuracies.
+  double max_accuracy = NS_MAX(oldAccuracy, newAccuracy);
+  if (delta > max_accuracy)
+    return true;
+
+  // check to see if the aSomewhere position is more accurate
+  if (oldAccuracy >= newAccuracy)
+    return true;
+
+  return false;
+}
+
 void
 nsGeolocationService::SetCachedPosition(nsIDOMGeoPosition* aPosition)
 {
   mLastPosition = aPosition;
 }
 
 nsIDOMGeoPosition*
 nsGeolocationService::GetCachedPosition()
@@ -1059,31 +1162,31 @@ nsGeolocation::RemoveRequest(nsGeolocati
   // ever called WatchPosition() and we do not want to mess
   // around with the ordering of the array.  Instead, just
   // mark the request as "cleared".
 
   aRequest->MarkCleared();
 }
 
 void
-nsGeolocation::Update(nsIDOMGeoPosition *aSomewhere)
+nsGeolocation::Update(nsIDOMGeoPosition *aSomewhere, bool aIsBetter)
 {
   if (!WindowOwnerStillExists()) {
     return Shutdown();
   }
 
   for (uint32_t i = mPendingCallbacks.Length(); i> 0; i--) {
-    if (mPendingCallbacks[i-1]->Update(aSomewhere)) {
+    if (mPendingCallbacks[i-1]->Update(aSomewhere, aIsBetter)) {
       mPendingCallbacks.RemoveElementAt(i-1);
     }
   }
 
   // notify everyone that is watching
   for (uint32_t i = 0; i< mWatchingCallbacks.Length(); i++) {
-    mWatchingCallbacks[i]->Update(aSomewhere);
+    mWatchingCallbacks[i]->Update(aSomewhere, aIsBetter);
   }
 }
 
 NS_IMETHODIMP
 nsGeolocation::GetCurrentPosition(nsIDOMGeoPositionCallback *callback,
                                   nsIDOMGeoPositionErrorCallback *errorCallback,
                                   const jsval& options,
                                   JSContext* cx)
--- a/dom/src/geolocation/nsGeolocation.h
+++ b/dom/src/geolocation/nsGeolocation.h
@@ -54,17 +54,18 @@ class nsGeolocationRequest
                        nsIDOMGeoPositionCallback* callback,
                        nsIDOMGeoPositionErrorCallback* errorCallback,
                        bool watchPositionRequest = false,
                        int32_t watchId = 0);
   nsresult Init(JSContext* aCx, const jsval& aOptions);
   void Shutdown();
 
   // Called by the geolocation device to notify that a location has changed.
-  bool Update(nsIDOMGeoPosition* aPosition);
+  // isBetter: the accuracy is as good or better than the previous position. 
+  bool Update(nsIDOMGeoPosition* aPosition, bool aIsBetter);
 
   void SendLocation(nsIDOMGeoPosition* location);
   void MarkCleared();
   bool IsActive() {return !mCleared;}
   bool Allowed() {return mAllowed;}
   void SetTimeoutTimer();
 
   ~nsGeolocationRequest();
@@ -74,16 +75,17 @@ class nsGeolocationRequest
 
   int32_t WatchId() { return mWatchId; }
 
  private:
 
   void NotifyError(int16_t errorCode);
   bool mAllowed;
   bool mCleared;
+  bool mIsFirstUpdate;
   bool mIsWatchPositionRequest;
 
   nsCOMPtr<nsITimer> mTimeoutTimer;
   nsCOMPtr<nsIDOMGeoPositionCallback> mCallback;
   nsCOMPtr<nsIDOMGeoPositionErrorCallback> mErrorCallback;
   nsAutoPtr<mozilla::dom::GeoPositionOptions> mOptions;
 
   nsRefPtr<nsGeolocation> mLocator;
@@ -115,16 +117,17 @@ public:
   void HandleMozsettingValue(const bool aValue);
 
   // Management of the nsGeolocation objects
   void AddLocator(nsGeolocation* locator);
   void RemoveLocator(nsGeolocation* locator);
 
   void SetCachedPosition(nsIDOMGeoPosition* aPosition);
   nsIDOMGeoPosition* GetCachedPosition();
+  PRBool IsBetterPosition(nsIDOMGeoPosition *aSomewhere);
 
   // Find and startup a geolocation device (gps, nmea, etc.)
   nsresult StartDevice();
 
   // Stop the started geolocation device (gps, nmea, etc.)
   void     StopDevice();
 
   // create, or reinitalize the callback timer
@@ -170,17 +173,17 @@ public:
 
   NS_DECL_CYCLE_COLLECTION_CLASS(nsGeolocation)
 
   nsGeolocation();
 
   nsresult Init(nsIDOMWindow* contentDom=nullptr);
 
   // Called by the geolocation device to notify that a location has changed.
-  void Update(nsIDOMGeoPosition* aPosition);
+  void Update(nsIDOMGeoPosition* aPosition, bool aIsBetter);
 
   // Returns true if any of the callbacks are repeating
   bool HasActiveCallbacks();
 
   // Remove request from all callbacks arrays
   void RemoveRequest(nsGeolocationRequest* request);
 
   // Shutting down.