2 * Copyright (C) 2008, 2009, 2010, 2011 Apple Inc. All Rights Reserved.
3 * Copyright (C) 2009 Torch Mobile, Inc.
4 * Copyright 2010, The Android Open Source Project
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #include "Geolocation.h"
31 #if ENABLE(GEOLOCATION)
36 #include "Geoposition.h"
38 #include <wtf/CurrentTime.h>
40 #if ENABLE(CLIENT_BASED_GEOLOCATION)
41 #include "Coordinates.h"
42 #include "GeolocationController.h"
43 #include "GeolocationError.h"
44 #include "GeolocationPosition.h"
45 #include "PositionError.h"
50 static const char permissionDeniedErrorMessage[] = "User denied Geolocation";
51 static const char failedToStartServiceErrorMessage[] = "Failed to start Geolocation service";
52 static const char framelessDocumentErrorMessage[] = "Geolocation cannot be used in frameless documents";
54 static const int firstAvailableWatchId = 1;
56 #if ENABLE(CLIENT_BASED_GEOLOCATION)
58 static PassRefPtr<Geoposition> createGeoposition(GeolocationPosition* position)
63 RefPtr<Coordinates> coordinates = Coordinates::create(position->latitude(), position->longitude(), position->canProvideAltitude(), position->altitude(),
64 position->accuracy(), position->canProvideAltitudeAccuracy(), position->altitudeAccuracy(),
65 position->canProvideHeading(), position->heading(), position->canProvideSpeed(), position->speed());
66 return Geoposition::create(coordinates.release(), convertSecondsToDOMTimeStamp(position->timestamp()));
69 static PassRefPtr<PositionError> createPositionError(GeolocationError* error)
71 PositionError::ErrorCode code = PositionError::POSITION_UNAVAILABLE;
72 switch (error->code()) {
73 case GeolocationError::PermissionDenied:
74 code = PositionError::PERMISSION_DENIED;
76 case GeolocationError::PositionUnavailable:
77 code = PositionError::POSITION_UNAVAILABLE;
81 return PositionError::create(code, error->message());
85 Geolocation::GeoNotifier::GeoNotifier(Geolocation* geolocation, PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options)
86 : m_geolocation(geolocation)
87 , m_successCallback(successCallback)
88 , m_errorCallback(errorCallback)
90 , m_timer(this, &Geolocation::GeoNotifier::timerFired)
91 , m_useCachedPosition(false)
93 ASSERT(m_geolocation);
94 ASSERT(m_successCallback);
95 // If no options were supplied from JS, we should have created a default set
96 // of options in JSGeolocationCustom.cpp.
100 void Geolocation::GeoNotifier::setFatalError(PassRefPtr<PositionError> error)
102 // If a fatal error has already been set, stick with it. This makes sure that
103 // when permission is denied, this is the error reported, as required by the
108 m_fatalError = error;
109 // An existing timer may not have a zero timeout.
111 m_timer.startOneShot(0);
114 void Geolocation::GeoNotifier::setUseCachedPosition()
116 m_useCachedPosition = true;
117 m_timer.startOneShot(0);
120 bool Geolocation::GeoNotifier::hasZeroTimeout() const
122 return m_options->hasTimeout() && m_options->timeout() == 0;
125 void Geolocation::GeoNotifier::runSuccessCallback(Geoposition* position)
127 m_successCallback->handleEvent(position);
130 void Geolocation::GeoNotifier::startTimerIfNeeded()
132 if (m_options->hasTimeout())
133 m_timer.startOneShot(m_options->timeout() / 1000.0);
136 void Geolocation::GeoNotifier::timerFired(Timer<GeoNotifier>*)
140 // Protect this GeoNotifier object, since it
141 // could be deleted by a call to clearWatch in a callback.
142 RefPtr<GeoNotifier> protect(this);
144 // Test for fatal error first. This is required for the case where the Frame is
145 // disconnected and requests are cancelled.
148 m_errorCallback->handleEvent(m_fatalError.get());
149 // This will cause this notifier to be deleted.
150 m_geolocation->fatalErrorOccurred(this);
154 if (m_useCachedPosition) {
155 // Clear the cached position flag in case this is a watch request, which
156 // will continue to run.
157 m_useCachedPosition = false;
158 m_geolocation->requestUsesCachedPosition(this);
162 if (m_errorCallback) {
163 RefPtr<PositionError> error = PositionError::create(PositionError::TIMEOUT, "Timeout expired");
164 m_errorCallback->handleEvent(error.get());
166 m_geolocation->requestTimedOut(this);
169 void Geolocation::Watchers::set(int id, PassRefPtr<GeoNotifier> prpNotifier)
172 RefPtr<GeoNotifier> notifier = prpNotifier;
174 m_idToNotifierMap.set(id, notifier.get());
175 m_notifierToIdMap.set(notifier.release(), id);
178 void Geolocation::Watchers::remove(int id)
181 IdToNotifierMap::iterator iter = m_idToNotifierMap.find(id);
182 if (iter == m_idToNotifierMap.end())
184 m_notifierToIdMap.remove(iter->second);
185 m_idToNotifierMap.remove(iter);
188 void Geolocation::Watchers::remove(GeoNotifier* notifier)
190 NotifierToIdMap::iterator iter = m_notifierToIdMap.find(notifier);
191 if (iter == m_notifierToIdMap.end())
193 m_idToNotifierMap.remove(iter->second);
194 m_notifierToIdMap.remove(iter);
197 bool Geolocation::Watchers::contains(GeoNotifier* notifier) const
199 return m_notifierToIdMap.contains(notifier);
202 void Geolocation::Watchers::clear()
204 m_idToNotifierMap.clear();
205 m_notifierToIdMap.clear();
208 bool Geolocation::Watchers::isEmpty() const
210 return m_idToNotifierMap.isEmpty();
213 void Geolocation::Watchers::getNotifiersVector(GeoNotifierVector& copy) const
215 copyValuesToVector(m_idToNotifierMap, copy);
218 Geolocation::Geolocation(Frame* frame)
220 #if !ENABLE(CLIENT_BASED_GEOLOCATION)
221 , m_service(GeolocationService::create(this))
223 , m_allowGeolocation(Unknown)
227 ASSERT(m_frame->document());
228 m_frame->document()->setUsingGeolocation(true);
231 Geolocation::~Geolocation()
233 ASSERT(m_allowGeolocation != InProgress);
237 Page* Geolocation::page() const
239 return m_frame ? m_frame->page() : 0;
242 void Geolocation::reset()
244 Page* page = this->page();
245 if (page && m_allowGeolocation == InProgress) {
246 #if ENABLE(CLIENT_BASED_GEOLOCATION)
247 page->geolocationController()->cancelPermissionRequest(this);
249 page->chrome()->cancelGeolocationPermissionRequestForFrame(m_frame, this);
252 // The frame may be moving to a new page and we want to get the permissions from the new page's client.
253 m_allowGeolocation = Unknown;
258 void Geolocation::disconnectFrame()
260 // Once we are disconnected from the Frame, it is no longer possible to perform any operations.
262 if (m_frame && m_frame->document())
263 m_frame->document()->setUsingGeolocation(false);
267 Geoposition* Geolocation::lastPosition()
269 #if ENABLE(CLIENT_BASED_GEOLOCATION)
270 Page* page = this->page();
274 m_lastPosition = createGeoposition(page->geolocationController()->lastPosition());
276 m_lastPosition = m_service->lastPosition();
279 return m_lastPosition.get();
282 void Geolocation::getCurrentPosition(PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options)
287 RefPtr<GeoNotifier> notifier = startRequest(successCallback, errorCallback, options);
290 m_oneShots.add(notifier);
293 int Geolocation::watchPosition(PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options)
298 RefPtr<GeoNotifier> notifier = startRequest(successCallback, errorCallback, options);
301 static int nextAvailableWatchId = firstAvailableWatchId;
302 // In case of overflow, make sure the ID remains positive, but reuse the ID values.
303 if (nextAvailableWatchId < 1)
304 nextAvailableWatchId = 1;
305 m_watchers.set(nextAvailableWatchId, notifier.release());
306 return nextAvailableWatchId++;
309 PassRefPtr<Geolocation::GeoNotifier> Geolocation::startRequest(PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options)
311 RefPtr<GeoNotifier> notifier = GeoNotifier::create(this, successCallback, errorCallback, options);
313 // Check whether permissions have already been denied. Note that if this is the case,
314 // the permission state can not change again in the lifetime of this page.
316 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
317 else if (haveSuitableCachedPosition(notifier->m_options.get()))
318 notifier->setUseCachedPosition();
319 else if (notifier->hasZeroTimeout())
320 notifier->startTimerIfNeeded();
321 #if USE(PREEMPT_GEOLOCATION_PERMISSION)
322 else if (!isAllowed()) {
323 // if we don't yet have permission, request for permission before calling startUpdating()
324 m_pendingForPermissionNotifiers.add(notifier);
328 else if (startUpdating(notifier.get()))
329 notifier->startTimerIfNeeded();
331 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
333 return notifier.release();
336 void Geolocation::fatalErrorOccurred(Geolocation::GeoNotifier* notifier)
338 // This request has failed fatally. Remove it from our lists.
339 m_oneShots.remove(notifier);
340 m_watchers.remove(notifier);
346 void Geolocation::requestUsesCachedPosition(GeoNotifier* notifier)
348 // This is called asynchronously, so the permissions could have been denied
349 // since we last checked in startRequest.
351 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
355 m_requestsAwaitingCachedPosition.add(notifier);
357 // If permissions are allowed, make the callback
359 makeCachedPositionCallbacks();
363 // Request permissions, which may be synchronous or asynchronous.
367 void Geolocation::makeCachedPositionCallbacks()
369 // All modifications to m_requestsAwaitingCachedPosition are done
370 // asynchronously, so we don't need to worry about it being modified from
372 GeoNotifierSet::const_iterator end = m_requestsAwaitingCachedPosition.end();
373 for (GeoNotifierSet::const_iterator iter = m_requestsAwaitingCachedPosition.begin(); iter != end; ++iter) {
374 GeoNotifier* notifier = iter->get();
375 notifier->runSuccessCallback(m_cachedPosition.get());
377 // If this is a one-shot request, stop it. Otherwise, if the watch still
378 // exists, start the service to get updates.
379 if (m_oneShots.contains(notifier))
380 m_oneShots.remove(notifier);
381 else if (m_watchers.contains(notifier)) {
382 if (notifier->hasZeroTimeout() || startUpdating(notifier))
383 notifier->startTimerIfNeeded();
385 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
389 m_requestsAwaitingCachedPosition.clear();
395 void Geolocation::requestTimedOut(GeoNotifier* notifier)
397 // If this is a one-shot request, stop it.
398 m_oneShots.remove(notifier);
404 bool Geolocation::haveSuitableCachedPosition(PositionOptions* options)
406 if (!m_cachedPosition)
408 if (!options->hasMaximumAge())
410 if (!options->maximumAge())
412 DOMTimeStamp currentTimeMillis = convertSecondsToDOMTimeStamp(currentTime());
413 return m_cachedPosition->timestamp() > currentTimeMillis - options->maximumAge();
416 void Geolocation::clearWatch(int watchId)
418 if (watchId < firstAvailableWatchId)
421 m_watchers.remove(watchId);
427 void Geolocation::setIsAllowed(bool allowed)
429 // Protect the Geolocation object from garbage collection during a callback.
430 RefPtr<Geolocation> protect(this);
432 // This may be due to either a new position from the service, or a cached
434 m_allowGeolocation = allowed ? Yes : No;
436 #if USE(PREEMPT_GEOLOCATION_PERMISSION)
437 // Permission request was made during the startRequest process
438 if (!m_pendingForPermissionNotifiers.isEmpty()) {
439 handlePendingPermissionNotifiers();
440 m_pendingForPermissionNotifiers.clear();
446 RefPtr<PositionError> error = PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage);
447 error->setIsFatal(true);
448 handleError(error.get());
449 m_requestsAwaitingCachedPosition.clear();
453 // If the service has a last position, use it to call back for all requests.
454 // If any of the requests are waiting for permission for a cached position,
455 // the position from the service will be at least as fresh.
457 makeSuccessCallbacks();
459 makeCachedPositionCallbacks();
462 void Geolocation::sendError(GeoNotifierVector& notifiers, PositionError* error)
464 GeoNotifierVector::const_iterator end = notifiers.end();
465 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) {
466 RefPtr<GeoNotifier> notifier = *it;
468 if (notifier->m_errorCallback)
469 notifier->m_errorCallback->handleEvent(error);
473 void Geolocation::sendPosition(GeoNotifierVector& notifiers, Geoposition* position)
475 GeoNotifierVector::const_iterator end = notifiers.end();
476 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) {
477 RefPtr<GeoNotifier> notifier = *it;
478 ASSERT(notifier->m_successCallback);
480 notifier->m_successCallback->handleEvent(position);
484 void Geolocation::stopTimer(GeoNotifierVector& notifiers)
486 GeoNotifierVector::const_iterator end = notifiers.end();
487 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) {
488 RefPtr<GeoNotifier> notifier = *it;
489 notifier->m_timer.stop();
493 void Geolocation::stopTimersForOneShots()
495 GeoNotifierVector copy;
496 copyToVector(m_oneShots, copy);
501 void Geolocation::stopTimersForWatchers()
503 GeoNotifierVector copy;
504 m_watchers.getNotifiersVector(copy);
509 void Geolocation::stopTimers()
511 stopTimersForOneShots();
512 stopTimersForWatchers();
515 void Geolocation::cancelRequests(GeoNotifierVector& notifiers)
517 GeoNotifierVector::const_iterator end = notifiers.end();
518 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
519 (*it)->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, framelessDocumentErrorMessage));
522 void Geolocation::cancelAllRequests()
524 GeoNotifierVector copy;
525 copyToVector(m_oneShots, copy);
526 cancelRequests(copy);
527 m_watchers.getNotifiersVector(copy);
528 cancelRequests(copy);
531 void Geolocation::extractNotifiersWithCachedPosition(GeoNotifierVector& notifiers, GeoNotifierVector* cached)
533 GeoNotifierVector nonCached;
534 GeoNotifierVector::iterator end = notifiers.end();
535 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) {
536 GeoNotifier* notifier = it->get();
537 if (notifier->m_useCachedPosition) {
539 cached->append(notifier);
541 nonCached.append(notifier);
543 notifiers.swap(nonCached);
546 void Geolocation::copyToSet(const GeoNotifierVector& src, GeoNotifierSet& dest)
548 GeoNotifierVector::const_iterator end = src.end();
549 for (GeoNotifierVector::const_iterator it = src.begin(); it != end; ++it) {
550 GeoNotifier* notifier = it->get();
555 void Geolocation::handleError(PositionError* error)
559 GeoNotifierVector oneShotsCopy;
560 copyToVector(m_oneShots, oneShotsCopy);
562 GeoNotifierVector watchersCopy;
563 m_watchers.getNotifiersVector(watchersCopy);
565 // Clear the lists before we make the callbacks, to avoid clearing notifiers
566 // added by calls to Geolocation methods from the callbacks, and to prevent
567 // further callbacks to these notifiers.
568 GeoNotifierVector oneShotsWithCachedPosition;
570 if (error->isFatal())
573 // Don't send non-fatal errors to notifiers due to receive a cached position.
574 extractNotifiersWithCachedPosition(oneShotsCopy, &oneShotsWithCachedPosition);
575 extractNotifiersWithCachedPosition(watchersCopy, 0);
578 sendError(oneShotsCopy, error);
579 sendError(watchersCopy, error);
581 // hasListeners() doesn't distinguish between notifiers due to receive a
582 // cached position and those requiring a fresh position. Perform the check
583 // before restoring the notifiers below.
587 // Maintain a reference to the cached notifiers until their timer fires.
588 copyToSet(oneShotsWithCachedPosition, m_oneShots);
591 void Geolocation::requestPermission()
593 if (m_allowGeolocation > Unknown)
596 Page* page = this->page();
600 m_allowGeolocation = InProgress;
602 // Ask the embedder: it maintains the geolocation challenge policy itself.
603 #if ENABLE(CLIENT_BASED_GEOLOCATION)
604 page->geolocationController()->requestPermission(this);
606 page->chrome()->requestGeolocationPermissionForFrame(m_frame, this);
610 void Geolocation::positionChangedInternal()
612 m_cachedPosition = lastPosition();
614 // Stop all currently running timers.
618 // requestPermission() will ask the chrome for permission. This may be
619 // implemented synchronously or asynchronously. In both cases,
620 // makeSuccessCallbacks() will be called if permission is granted, so
621 // there's nothing more to do here.
626 makeSuccessCallbacks();
629 void Geolocation::makeSuccessCallbacks()
631 ASSERT(lastPosition());
634 GeoNotifierVector oneShotsCopy;
635 copyToVector(m_oneShots, oneShotsCopy);
637 GeoNotifierVector watchersCopy;
638 m_watchers.getNotifiersVector(watchersCopy);
640 // Clear the lists before we make the callbacks, to avoid clearing notifiers
641 // added by calls to Geolocation methods from the callbacks, and to prevent
642 // further callbacks to these notifiers.
645 sendPosition(oneShotsCopy, lastPosition());
646 sendPosition(watchersCopy, lastPosition());
652 #if ENABLE(CLIENT_BASED_GEOLOCATION)
654 void Geolocation::positionChanged()
656 positionChangedInternal();
659 void Geolocation::setError(GeolocationError* error)
661 RefPtr<PositionError> positionError = createPositionError(error);
662 handleError(positionError.get());
667 void Geolocation::geolocationServicePositionChanged(GeolocationService* service)
669 ASSERT_UNUSED(service, service == m_service);
670 ASSERT(m_service->lastPosition());
672 positionChangedInternal();
675 void Geolocation::geolocationServiceErrorOccurred(GeolocationService* service)
677 ASSERT(service->lastError());
679 // Note that we do not stop timers here. For one-shots, the request is
680 // cleared in handleError. For watchers, the spec requires that the timer is
682 handleError(service->lastError());
687 bool Geolocation::startUpdating(GeoNotifier* notifier)
689 #if ENABLE(CLIENT_BASED_GEOLOCATION)
690 Page* page = this->page();
694 page->geolocationController()->addObserver(this, notifier->m_options->enableHighAccuracy());
697 return m_service->startUpdating(notifier->m_options.get());
701 void Geolocation::stopUpdating()
703 #if ENABLE(CLIENT_BASED_GEOLOCATION)
704 Page* page = this->page();
708 page->geolocationController()->removeObserver(this);
710 m_service->stopUpdating();
715 #if USE(PREEMPT_GEOLOCATION_PERMISSION)
716 void Geolocation::handlePendingPermissionNotifiers()
718 // While we iterate through the list, we need not worry about list being modified as the permission
719 // is already set to Yes/No and no new listeners will be added to the pending list
720 GeoNotifierSet::const_iterator end = m_pendingForPermissionNotifiers.end();
721 for (GeoNotifierSet::const_iterator iter = m_pendingForPermissionNotifiers.begin(); iter != end; ++iter) {
722 GeoNotifier* notifier = iter->get();
725 // start all pending notification requests as permission granted.
726 // The notifier is always ref'ed by m_oneShots or m_watchers.
727 if (startUpdating(notifier))
728 notifier->startTimerIfNeeded();
730 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
732 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
737 } // namespace WebCore
743 void Geolocation::clearWatch(int) {}
745 void Geolocation::reset() {}
747 void Geolocation::disconnectFrame() {}
749 Geolocation::Geolocation(Frame*) {}
751 Geolocation::~Geolocation() {}
753 void Geolocation::setIsAllowed(bool) {}
757 #endif // ENABLE(GEOLOCATION)