Implements Geolocation PositionOptions.maximumAge property.

This fixes bug http://b/issue?id=2054431.

Change-Id: Ic5fcd763e448b3bdc02de5d2a40d418ef25d39f9
diff --git a/WebCore/page/Geolocation.cpp b/WebCore/page/Geolocation.cpp
index 359c125..adf5eb1 100644
--- a/WebCore/page/Geolocation.cpp
+++ b/WebCore/page/Geolocation.cpp
@@ -28,6 +28,7 @@
 #include "Geolocation.h"
 
 #include "Chrome.h"
+#include "CurrentTime.h"
 #include "Document.h"
 #include "Frame.h"
 #include "Page.h"
@@ -57,6 +58,13 @@
     m_timer.startOneShot(0);
 }
 
+void Geolocation::GeoNotifier::setCachedPosition(Geoposition* cachedPosition)
+{
+    // We do not take owenership from the caller, but add our own ref count.
+    m_cachedPosition = cachedPosition;
+    m_timer.startOneShot(0);
+}
+
 void Geolocation::GeoNotifier::startTimerIfNeeded()
 {
     if (m_options->hasTimeout())
@@ -68,14 +76,19 @@
     m_timer.stop();
 
     if (m_fatalError) {
-        if (m_errorCallback) {
+        if (m_errorCallback)
             m_errorCallback->handleEvent(m_fatalError.get());
-        }
         // This will cause this notifier to be deleted.
         m_geolocation->fatalErrorOccurred(this);
         return;
     }
 
+    if (m_cachedPosition) {
+        m_successCallback->handleEvent(m_cachedPosition.get());
+        m_geolocation->requestReturnedCachedPosition(this);
+        return;
+    }
+
     if (m_errorCallback) {
         RefPtr<PositionError> error = PositionError::create(PositionError::TIMEOUT, "Timed out");
         m_errorCallback->handleEvent(error.get());
@@ -83,11 +96,45 @@
     m_geolocation->requestTimedOut(this);
 }
 
+class CachedPositionManager {
+  public:
+    CachedPositionManager()
+    {
+        if (s_instances++ == 0)
+            s_cachedPosition = new RefPtr<Geoposition>;
+    }
+    ~CachedPositionManager()
+    {
+        if (--s_instances == 0) {
+            // TODO(steveblock): Store cached position between sessions.
+            delete s_cachedPosition;
+        }
+    }
+    void setCachedPosition(Geoposition* cachedPosition)
+    {
+        // We do not take owenership from the caller, but add our own ref count.
+        *s_cachedPosition = cachedPosition;
+    }
+    Geoposition* cachedPosition()
+    {
+        return s_cachedPosition->get();
+    }
+
+ private:
+    static int s_instances;
+    static RefPtr<Geoposition>* s_cachedPosition;
+};
+
+int CachedPositionManager::s_instances = 0;
+RefPtr<Geoposition>* CachedPositionManager::s_cachedPosition;
+
+
 Geolocation::Geolocation(Frame* frame)
     : m_frame(frame)
     , m_service(GeolocationService::create(this))
     , m_allowGeolocation(Unknown)
     , m_shouldClearCache(false)
+    , m_cachedPositionManager(new CachedPositionManager)
 {
     if (!m_frame)
         return;
@@ -101,6 +148,8 @@
     if (m_frame && m_frame->document())
         m_frame->document()->setUsingGeolocation(false);
     m_frame = 0;
+
+    delete m_cachedPositionManager;
 }
 
 void Geolocation::getCurrentPosition(PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options)
@@ -131,10 +180,19 @@
     if (isDenied()) {
         notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
     } else {
-        if (m_service->startUpdating(notifier->m_options.get()))
-            notifier->startTimerIfNeeded();
-        else {
-            notifier->setFatalError(PositionError::create(PositionError::UNKNOWN_ERROR, "Failed to start Geolocation service"));
+        if (haveSuitableCachedPosition(notifier->m_options.get())) {
+            ASSERT(m_cachedPositionManager->cachedPosition());
+            if (isAllowed())
+                notifier->setCachedPosition(m_cachedPositionManager->cachedPosition());
+            else {
+                m_requestsAwaitingCachedPosition.add(notifier);
+                requestPermission();
+            }
+        } else {
+            if (m_service->startUpdating(notifier->m_options.get()))
+                notifier->startTimerIfNeeded();
+            else
+                notifier->setFatalError(PositionError::create(PositionError::UNKNOWN_ERROR, "Failed to start Geolocation service"));
         }
     }
 
@@ -165,6 +223,35 @@
         m_service->stopUpdating();
 }
 
+void Geolocation::requestReturnedCachedPosition(GeoNotifier* notifier)
+{
+    // If this is a one-shot request, stop it.
+    if (m_oneShots.contains(notifier)) {
+        m_oneShots.remove(notifier);
+        if (!hasListeners())
+            m_service->stopUpdating();
+        return;
+    }
+
+    // Otherwise, start the service to get updates.
+    if (m_service->startUpdating(notifier->m_options.get()))
+        notifier->startTimerIfNeeded();
+    else
+        notifier->setFatalError(PositionError::create(PositionError::UNKNOWN_ERROR, "Failed to start Geolocation service"));
+}
+
+bool Geolocation::haveSuitableCachedPosition(PositionOptions* options)
+{
+    if (m_cachedPositionManager->cachedPosition() == 0)
+        return false;
+    if (!options->hasMaximumAge())
+        return true;
+    if (options->maximumAge() == 0)
+        return false;
+    DOMTimeStamp currentTimeMillis = currentTime() * 1000.0;
+    return m_cachedPositionManager->cachedPosition()->timestamp() > currentTimeMillis - options->maximumAge();
+}
+
 void Geolocation::clearWatch(int watchId)
 {
     m_watchers.remove(watchId);
@@ -187,15 +274,28 @@
 
 void Geolocation::setIsAllowed(bool allowed)
 {
+    // This may be due to either a new position from the service, or a cached
+    // position.
     m_allowGeolocation = allowed ? Yes : No;
-    
-    if (isAllowed()) {
-        makeSuccessCallbacks();
-    } else {
-        WTF::RefPtr<WebCore::PositionError> error = PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage);
+
+    if (!isAllowed()) {
+        RefPtr<WebCore::PositionError> error = PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage);
         error->setIsFatal(true);
         handleError(error.get());
+        return;
     }
+
+    // If the service has a last position, use it to call back for all requests.
+    // If any of the requests are waiting for permission for a cached position,
+    // the position from the service will be at least as fresh.
+    if (m_service->lastPosition())
+        makeSuccessCallbacks();
+    else {
+        GeoNotifierSet::const_iterator end = m_requestsAwaitingCachedPosition.end();
+        for (GeoNotifierSet::const_iterator iter = m_requestsAwaitingCachedPosition.begin(); iter != end; ++iter)
+            (*iter)->setCachedPosition(m_cachedPositionManager->cachedPosition());
+    }
+    m_requestsAwaitingCachedPosition.clear();
 }
 
 void Geolocation::sendError(Vector<RefPtr<GeoNotifier> >& notifiers, PositionError* error)
@@ -297,6 +397,8 @@
 {
     ASSERT(m_service->lastPosition());
 
+    m_cachedPositionManager->setCachedPosition(m_service->lastPosition());
+
     // Stop all currently running timers.
     stopTimers();
 
diff --git a/WebCore/page/Geolocation.h b/WebCore/page/Geolocation.h
index f74c87b..0a19ec2 100644
--- a/WebCore/page/Geolocation.h
+++ b/WebCore/page/Geolocation.h
@@ -27,6 +27,7 @@
 #define Geolocation_h
 
 #include "GeolocationService.h"
+#include "Geoposition.h"
 #include "PositionCallback.h"
 #include "PositionError.h"
 #include "PositionErrorCallback.h"
@@ -44,7 +45,8 @@
 namespace WebCore {
 
 class Frame;
-class Geoposition;
+class CachedPositionManager;
+
 
 class Geolocation : public RefCounted<Geolocation>, public GeolocationServiceClient {
 public:
@@ -78,6 +80,7 @@
         static PassRefPtr<GeoNotifier> create(Geolocation* geolocation, PassRefPtr<PositionCallback> positionCallback, PassRefPtr<PositionErrorCallback> positionErrorCallback, PassRefPtr<PositionOptions> options) { return adoptRef(new GeoNotifier(geolocation, positionCallback, positionErrorCallback, options)); }
         
         void setFatalError(PassRefPtr<PositionError> error);
+        void setCachedPosition(Geoposition* cachedPosition);
         void startTimerIfNeeded();
         void timerFired(Timer<GeoNotifier>*);
         
@@ -87,6 +90,7 @@
         RefPtr<PositionOptions> m_options;
         Timer<GeoNotifier> m_timer;
         RefPtr<PositionError> m_fatalError;
+        RefPtr<Geoposition> m_cachedPosition;
 
     private:
         GeoNotifier(Geolocation* geolocation, PassRefPtr<PositionCallback>, PassRefPtr<PositionErrorCallback>, PassRefPtr<PositionOptions>);
@@ -114,6 +118,8 @@
 
     void fatalErrorOccurred(GeoNotifier* notifier);
     void requestTimedOut(GeoNotifier* notifier);
+    void requestReturnedCachedPosition(GeoNotifier* notifier);
+    bool haveSuitableCachedPosition(PositionOptions*);
 
     typedef HashSet<RefPtr<GeoNotifier> > GeoNotifierSet;
     typedef HashMap<int, RefPtr<GeoNotifier> > GeoNotifierMap;
@@ -130,6 +136,9 @@
         No
     } m_allowGeolocation;
     bool m_shouldClearCache;
+
+    CachedPositionManager* m_cachedPositionManager;
+    GeoNotifierSet m_requestsAwaitingCachedPosition;
 };
     
 } // namespace WebCore