diff options
Diffstat (limited to 'WebCore/page/Geolocation.cpp')
-rw-r--r-- | WebCore/page/Geolocation.cpp | 159 |
1 files changed, 110 insertions, 49 deletions
diff --git a/WebCore/page/Geolocation.cpp b/WebCore/page/Geolocation.cpp index bf877e6..978a1b0 100644 --- a/WebCore/page/Geolocation.cpp +++ b/WebCore/page/Geolocation.cpp @@ -28,7 +28,6 @@ #include "Geolocation.h" #include "Chrome.h" -#include "CurrentTime.h" #include "Document.h" #include "DOMWindow.h" #include "EventNames.h" @@ -38,10 +37,11 @@ #include "SQLiteStatement.h" #include "SQLiteTransaction.h" #include "SQLValue.h" +#include <wtf/CurrentTime.h> namespace WebCore { -static const char* permissionDeniedErrorMessage = "User denied Geolocation"; +static const char permissionDeniedErrorMessage[] = "User denied Geolocation"; Geolocation::GeoNotifier::GeoNotifier(Geolocation* geolocation, PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options) : m_geolocation(geolocation) @@ -49,7 +49,6 @@ Geolocation::GeoNotifier::GeoNotifier(Geolocation* geolocation, PassRefPtr<Posit , m_errorCallback(errorCallback) , m_options(options) , m_timer(this, &Geolocation::GeoNotifier::timerFired) - , m_fatalError(0) { ASSERT(m_geolocation); ASSERT(m_successCallback); @@ -60,10 +59,17 @@ Geolocation::GeoNotifier::GeoNotifier(Geolocation* geolocation, PassRefPtr<Posit void Geolocation::GeoNotifier::setFatalError(PassRefPtr<PositionError> error) { + // This method is called at most once on a given GeoNotifier object. + ASSERT(!m_fatalError); m_fatalError = error; m_timer.startOneShot(0); } +bool Geolocation::GeoNotifier::hasZeroTimeout() const +{ + return m_options->hasTimeout() && m_options->timeout() == 0; +} + void Geolocation::GeoNotifier::setCachedPosition(Geoposition* cachedPosition) { // We do not take owenership from the caller, but add our own ref count. @@ -81,25 +87,69 @@ void Geolocation::GeoNotifier::timerFired(Timer<GeoNotifier>*) { m_timer.stop(); + // Cache our pointer to the Geolocation object, as this object could be + // deleted by a call to clearWatch in a callback. + Geolocation* geolocation = m_geolocation; + if (m_fatalError) { if (m_errorCallback) m_errorCallback->handleEvent(m_fatalError.get()); // This will cause this notifier to be deleted. - m_geolocation->fatalErrorOccurred(this); + geolocation->fatalErrorOccurred(this); return; } if (m_cachedPosition) { m_successCallback->handleEvent(m_cachedPosition.get()); - m_geolocation->requestReturnedCachedPosition(this); + geolocation->requestReturnedCachedPosition(this); return; } if (m_errorCallback) { - RefPtr<PositionError> error = PositionError::create(PositionError::TIMEOUT, "Timed out"); + RefPtr<PositionError> error = PositionError::create(PositionError::TIMEOUT, "Timeout expired"); m_errorCallback->handleEvent(error.get()); } - m_geolocation->requestTimedOut(this); + geolocation->requestTimedOut(this); +} + +void Geolocation::Watchers::set(int id, PassRefPtr<GeoNotifier> notifier) +{ + m_idToNotifierMap.set(id, notifier); + m_notifierToIdMap.set(notifier, id); +} + +void Geolocation::Watchers::remove(int id) +{ + IdToNotifierMap::iterator iter = m_idToNotifierMap.find(id); + if (iter == m_idToNotifierMap.end()) + return; + m_notifierToIdMap.remove(iter->second); + m_idToNotifierMap.remove(iter); +} + +void Geolocation::Watchers::remove(GeoNotifier* notifier) +{ + NotifierToIdMap::iterator iter = m_notifierToIdMap.find(notifier); + if (iter == m_notifierToIdMap.end()) + return; + m_idToNotifierMap.remove(iter->second); + m_notifierToIdMap.remove(iter); +} + +void Geolocation::Watchers::clear() +{ + m_idToNotifierMap.clear(); + m_notifierToIdMap.clear(); +} + +bool Geolocation::Watchers::isEmpty() const +{ + return m_idToNotifierMap.isEmpty(); +} + +void Geolocation::Watchers::getNotifiersVector(Vector<RefPtr<GeoNotifier> >& copy) const +{ + copyValuesToVector(m_idToNotifierMap, copy); } static const char* databaseName = "/CachedPosition.db"; @@ -132,7 +182,9 @@ class CachedPositionManager { } static void setDatabasePath(String databasePath) { - s_databaseFile = databasePath + databaseName; + if (!s_databaseFile) + s_databaseFile = new String; + *s_databaseFile = databasePath + databaseName; // If we don't have have a cached position, attempt to read one from the // DB at the new path. if (s_instances && *s_cachedPosition == 0) @@ -143,7 +195,7 @@ class CachedPositionManager { static PassRefPtr<Geoposition> readFromDB() { SQLiteDatabase database; - if (!database.open(s_databaseFile)) + if (!s_databaseFile || !database.open(*s_databaseFile)) return 0; // Create the table here, such that even if we've just created the @@ -184,7 +236,7 @@ class CachedPositionManager { ASSERT(position); SQLiteDatabase database; - if (!database.open(s_databaseFile)) + if (!s_databaseFile || !database.open(*s_databaseFile)) return; SQLiteTransaction transaction(database); @@ -232,16 +284,17 @@ class CachedPositionManager { } static int s_instances; static RefPtr<Geoposition>* s_cachedPosition; - static String s_databaseFile; + static String* s_databaseFile; }; int CachedPositionManager::s_instances = 0; RefPtr<Geoposition>* CachedPositionManager::s_cachedPosition; -String CachedPositionManager::s_databaseFile; +String* CachedPositionManager::s_databaseFile = 0; Geolocation::Geolocation(Frame* frame) - : m_frame(frame) + : EventListener(GeolocationEventListenerType) + , m_frame(frame) , m_service(GeolocationService::create(this)) , m_allowGeolocation(Unknown) , m_shouldClearCache(false) @@ -274,7 +327,7 @@ void Geolocation::disconnectFrame() void Geolocation::getCurrentPosition(PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options) { - RefPtr<GeoNotifier> notifier = makeRequest(successCallback, errorCallback, options); + RefPtr<GeoNotifier> notifier = startRequest(successCallback, errorCallback, options); ASSERT(notifier); m_oneShots.add(notifier); @@ -282,24 +335,26 @@ void Geolocation::getCurrentPosition(PassRefPtr<PositionCallback> successCallbac int Geolocation::watchPosition(PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options) { - RefPtr<GeoNotifier> notifier = makeRequest(successCallback, errorCallback, options); + RefPtr<GeoNotifier> notifier = startRequest(successCallback, errorCallback, options); ASSERT(notifier); - static int sIdentifier = 0; - m_watchers.set(++sIdentifier, notifier); - - return sIdentifier; + static int nextAvailableWatchId = 1; + // In case of overflow, make sure the ID remains positive, but reuse the ID values. + if (nextAvailableWatchId < 1) + nextAvailableWatchId = 1; + m_watchers.set(nextAvailableWatchId, notifier.release()); + return nextAvailableWatchId++; } -PassRefPtr<Geolocation::GeoNotifier> Geolocation::makeRequest(PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options) +PassRefPtr<Geolocation::GeoNotifier> Geolocation::startRequest(PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options) { RefPtr<GeoNotifier> notifier = GeoNotifier::create(this, successCallback, errorCallback, options); // Check whether permissions have already been denied. Note that if this is the case, // the permission state can not change again in the lifetime of this page. - if (isDenied()) { + if (isDenied()) notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage)); - } else { + else { if (haveSuitableCachedPosition(notifier->m_options.get())) { ASSERT(m_cachedPositionManager->cachedPosition()); if (isAllowed()) @@ -309,7 +364,7 @@ PassRefPtr<Geolocation::GeoNotifier> Geolocation::makeRequest(PassRefPtr<Positio requestPermission(); } } else { - if (m_service->startUpdating(notifier->m_options.get())) + if (notifier->hasZeroTimeout() || m_service->startUpdating(notifier->m_options.get())) notifier->startTimerIfNeeded(); else notifier->setFatalError(PositionError::create(PositionError::UNKNOWN_ERROR, "Failed to start Geolocation service")); @@ -323,12 +378,7 @@ void Geolocation::fatalErrorOccurred(Geolocation::GeoNotifier* notifier) { // This request has failed fatally. Remove it from our lists. m_oneShots.remove(notifier); - for (GeoNotifierMap::iterator iter = m_watchers.begin(); iter != m_watchers.end(); ++iter) { - if (iter->second == notifier) { - m_watchers.remove(iter); - break; - } - } + m_watchers.remove(notifier); if (!hasListeners()) m_service->stopUpdating(); @@ -399,7 +449,7 @@ void Geolocation::setIsAllowed(bool allowed) m_allowGeolocation = allowed ? Yes : No; if (!isAllowed()) { - RefPtr<WebCore::PositionError> error = PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage); + RefPtr<PositionError> error = PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage); error->setIsFatal(true); handleError(error.get()); return; @@ -461,7 +511,7 @@ void Geolocation::stopTimersForOneShots() void Geolocation::stopTimersForWatchers() { Vector<RefPtr<GeoNotifier> > copy; - copyValuesToVector(m_watchers, copy); + m_watchers.getNotifiersVector(copy); stopTimer(copy); } @@ -475,15 +525,16 @@ void Geolocation::stopTimers() void Geolocation::handleError(PositionError* error) { ASSERT(error); - + Vector<RefPtr<GeoNotifier> > oneShotsCopy; copyToVector(m_oneShots, oneShotsCopy); Vector<RefPtr<GeoNotifier> > watchersCopy; - copyValuesToVector(m_watchers, watchersCopy); + m_watchers.getNotifiersVector(watchersCopy); // Clear the lists before we make the callbacks, to avoid clearing notifiers - // added by calls to Geolocation methods from the callbacks. + // added by calls to Geolocation methods from the callbacks, and to prevent + // further callbacks to these notifiers. m_oneShots.clear(); if (error->isFatal()) m_watchers.clear(); @@ -513,19 +564,20 @@ void Geolocation::requestPermission() page->chrome()->requestGeolocationPermissionForFrame(m_frame, this); } -void Geolocation::geolocationServicePositionChanged(GeolocationService*) +void Geolocation::geolocationServicePositionChanged(GeolocationService* service) { + ASSERT_UNUSED(service, service == m_service); ASSERT(m_service->lastPosition()); m_cachedPositionManager->setCachedPosition(m_service->lastPosition()); // Stop all currently running timers. stopTimers(); - + if (!isAllowed()) { // requestPermission() will ask the chrome for permission. This may be // implemented synchronously or asynchronously. In both cases, - // makeSucessCallbacks() will be called if permission is granted, so + // makeSuccessCallbacks() will be called if permission is granted, so // there's nothing more to do here. requestPermission(); return; @@ -538,15 +590,16 @@ void Geolocation::makeSuccessCallbacks() { ASSERT(m_service->lastPosition()); ASSERT(isAllowed()); - + Vector<RefPtr<GeoNotifier> > oneShotsCopy; copyToVector(m_oneShots, oneShotsCopy); - + Vector<RefPtr<GeoNotifier> > watchersCopy; - copyValuesToVector(m_watchers, watchersCopy); - + m_watchers.getNotifiersVector(watchersCopy); + // Clear the lists before we make the callbacks, to avoid clearing notifiers - // added by calls to Geolocation methods from the callbacks. + // added by calls to Geolocation methods from the callbacks, and to prevent + // further callbacks to these notifiers. m_oneShots.clear(); sendPosition(oneShotsCopy, m_service->lastPosition()); @@ -563,14 +616,22 @@ void Geolocation::geolocationServiceErrorOccurred(GeolocationService* service) handleError(service->lastError()); } -void Geolocation::handleEvent(Event* event, bool) +bool Geolocation::operator==(const EventListener& listener) { - ASSERT_UNUSED(event, event->type() == eventTypes().unloadEvent); - // Cancel any ongoing requests on page unload. This is required to release - // references to JS callbacks in the page, to allow the frame to be cleaned up - // by WebKit. - m_oneShots.clear(); - m_watchers.clear(); + if (listener.type() != GeolocationEventListenerType) + return false; + const Geolocation* geolocation = static_cast<const Geolocation*>(&listener); + return m_frame == geolocation->m_frame; +} + +void Geolocation::handleEvent(ScriptExecutionContext*, Event* event) +{ + ASSERT_UNUSED(event, event->type() == eventNames().unloadEvent); + // Cancel any ongoing requests on page unload. This is required to release + // references to JS callbacks in the page, to allow the frame to be cleaned up + // by WebKit. + m_oneShots.clear(); + m_watchers.clear(); } void Geolocation::setDatabasePath(String databasePath) |