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)
34 #include "ChromeClient.h"
37 #include "Geoposition.h"
39 #include <wtf/CurrentTime.h>
41 #if ENABLE(CLIENT_BASED_GEOLOCATION)
42 #include "Coordinates.h"
43 #include "GeolocationController.h"
44 #include "GeolocationError.h"
45 #include "GeolocationPosition.h"
46 #include "PositionError.h"
51 static const char permissionDeniedErrorMessage[] = "User denied Geolocation";
52 static const char failedToStartServiceErrorMessage[] = "Failed to start Geolocation service";
53 static const char framelessDocumentErrorMessage[] = "Geolocation cannot be used in frameless documents";
55 static const int firstAvailableWatchId = 1;
57 #if ENABLE(CLIENT_BASED_GEOLOCATION)
59 static PassRefPtr<Geoposition> createGeoposition(GeolocationPosition* position)
64 RefPtr<Coordinates> coordinates = Coordinates::create(position->latitude(), position->longitude(), position->canProvideAltitude(), position->altitude(),
65 position->accuracy(), position->canProvideAltitudeAccuracy(), position->altitudeAccuracy(),
66 position->canProvideHeading(), position->heading(), position->canProvideSpeed(), position->speed());
67 return Geoposition::create(coordinates.release(), convertSecondsToDOMTimeStamp(position->timestamp()));
70 static PassRefPtr<PositionError> createPositionError(GeolocationError* error)
72 PositionError::ErrorCode code = PositionError::POSITION_UNAVAILABLE;
73 switch (error->code()) {
74 case GeolocationError::PermissionDenied:
75 code = PositionError::PERMISSION_DENIED;
77 case GeolocationError::PositionUnavailable:
78 code = PositionError::POSITION_UNAVAILABLE;
82 return PositionError::create(code, error->message());
86 Geolocation::GeoNotifier::GeoNotifier(Geolocation* geolocation, PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options)
87 : m_geolocation(geolocation)
88 , m_successCallback(successCallback)
89 , m_errorCallback(errorCallback)
91 , m_timer(this, &Geolocation::GeoNotifier::timerFired)
92 , m_useCachedPosition(false)
94 ASSERT(m_geolocation);
95 ASSERT(m_successCallback);
96 // If no options were supplied from JS, we should have created a default set
97 // of options in JSGeolocationCustom.cpp.
101 void Geolocation::GeoNotifier::setFatalError(PassRefPtr<PositionError> error)
103 // If a fatal error has already been set, stick with it. This makes sure that
104 // when permission is denied, this is the error reported, as required by the
109 m_fatalError = error;
110 // An existing timer may not have a zero timeout.
112 m_timer.startOneShot(0);
115 void Geolocation::GeoNotifier::setUseCachedPosition()
117 m_useCachedPosition = true;
118 m_timer.startOneShot(0);
121 bool Geolocation::GeoNotifier::hasZeroTimeout() const
123 return m_options->hasTimeout() && m_options->timeout() == 0;
126 void Geolocation::GeoNotifier::runSuccessCallback(Geoposition* position)
128 m_successCallback->handleEvent(position);
131 void Geolocation::GeoNotifier::startTimerIfNeeded()
133 if (m_options->hasTimeout())
134 m_timer.startOneShot(m_options->timeout() / 1000.0);
137 void Geolocation::GeoNotifier::timerFired(Timer<GeoNotifier>*)
141 // Protect this GeoNotifier object, since it
142 // could be deleted by a call to clearWatch in a callback.
143 RefPtr<GeoNotifier> protect(this);
145 // Test for fatal error first. This is required for the case where the Frame is
146 // disconnected and requests are cancelled.
149 m_errorCallback->handleEvent(m_fatalError.get());
150 // This will cause this notifier to be deleted.
151 m_geolocation->fatalErrorOccurred(this);
155 if (m_useCachedPosition) {
156 // Clear the cached position flag in case this is a watch request, which
157 // will continue to run.
158 m_useCachedPosition = false;
159 m_geolocation->requestUsesCachedPosition(this);
163 if (m_errorCallback) {
164 RefPtr<PositionError> error = PositionError::create(PositionError::TIMEOUT, "Timeout expired");
165 m_errorCallback->handleEvent(error.get());
167 m_geolocation->requestTimedOut(this);
170 void Geolocation::Watchers::set(int id, PassRefPtr<GeoNotifier> prpNotifier)
173 RefPtr<GeoNotifier> notifier = prpNotifier;
175 m_idToNotifierMap.set(id, notifier.get());
176 m_notifierToIdMap.set(notifier.release(), id);
179 Geolocation::GeoNotifier* Geolocation::Watchers::find(int id)
182 IdToNotifierMap::iterator iter = m_idToNotifierMap.find(id);
183 if (iter == m_idToNotifierMap.end())
185 return iter->second.get();
188 void Geolocation::Watchers::remove(int id)
191 IdToNotifierMap::iterator iter = m_idToNotifierMap.find(id);
192 if (iter == m_idToNotifierMap.end())
194 m_notifierToIdMap.remove(iter->second);
195 m_idToNotifierMap.remove(iter);
198 void Geolocation::Watchers::remove(GeoNotifier* notifier)
200 NotifierToIdMap::iterator iter = m_notifierToIdMap.find(notifier);
201 if (iter == m_notifierToIdMap.end())
203 m_idToNotifierMap.remove(iter->second);
204 m_notifierToIdMap.remove(iter);
207 bool Geolocation::Watchers::contains(GeoNotifier* notifier) const
209 return m_notifierToIdMap.contains(notifier);
212 void Geolocation::Watchers::clear()
214 m_idToNotifierMap.clear();
215 m_notifierToIdMap.clear();
218 bool Geolocation::Watchers::isEmpty() const
220 return m_idToNotifierMap.isEmpty();
223 void Geolocation::Watchers::getNotifiersVector(GeoNotifierVector& copy) const
225 copyValuesToVector(m_idToNotifierMap, copy);
228 PassRefPtr<Geolocation> Geolocation::create(ScriptExecutionContext* context)
230 RefPtr<Geolocation> geolocation = adoptRef(new Geolocation(context));
231 geolocation->suspendIfNeeded();
232 return geolocation.release();
235 Geolocation::Geolocation(ScriptExecutionContext* context)
236 : ActiveDOMObject(context, this)
237 #if !ENABLE(CLIENT_BASED_GEOLOCATION)
238 , m_service(GeolocationService::create(this))
240 , m_allowGeolocation(Unknown)
244 Geolocation::~Geolocation()
246 ASSERT(m_allowGeolocation != InProgress);
249 Document* Geolocation::document() const
251 ASSERT(!scriptExecutionContext() || scriptExecutionContext()->isDocument());
252 return static_cast<Document*>(scriptExecutionContext());
255 Frame* Geolocation::frame() const
257 return document() ? document()->frame() : 0;
260 Page* Geolocation::page() const
262 return document() ? document()->page() : 0;
265 void Geolocation::stop()
267 // FIXME: We should ideally allow existing Geolocation activities to continue
268 // when the Geolocation's iframe is reparented. (Assuming we continue to
269 // support reparenting iframes.)
270 // See https://bugs.webkit.org/show_bug.cgi?id=55577
271 // and https://bugs.webkit.org/show_bug.cgi?id=52877
273 Page* page = this->page();
274 if (page && m_allowGeolocation == InProgress) {
275 #if ENABLE(CLIENT_BASED_GEOLOCATION)
276 page->geolocationController()->cancelPermissionRequest(this);
278 page->chrome()->client()->cancelGeolocationPermissionRequestForFrame(frame(), this);
281 // The frame may be moving to a new page and we want to get the permissions from the new page's client.
282 m_allowGeolocation = Unknown;
285 #if USE(PREEMPT_GEOLOCATION_PERMISSION)
286 m_pendingForPermissionNotifiers.clear();
290 Geoposition* Geolocation::lastPosition()
292 #if ENABLE(CLIENT_BASED_GEOLOCATION)
293 Page* page = this->page();
297 m_lastPosition = createGeoposition(page->geolocationController()->lastPosition());
299 m_lastPosition = m_service->lastPosition();
302 return m_lastPosition.get();
305 void Geolocation::getCurrentPosition(PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options)
310 RefPtr<GeoNotifier> notifier = startRequest(successCallback, errorCallback, options);
313 m_oneShots.add(notifier);
316 int Geolocation::watchPosition(PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options)
321 RefPtr<GeoNotifier> notifier = startRequest(successCallback, errorCallback, options);
324 static int nextAvailableWatchId = firstAvailableWatchId;
325 // In case of overflow, make sure the ID remains positive, but reuse the ID values.
326 if (nextAvailableWatchId < 1)
327 nextAvailableWatchId = 1;
328 m_watchers.set(nextAvailableWatchId, notifier.release());
329 return nextAvailableWatchId++;
332 PassRefPtr<Geolocation::GeoNotifier> Geolocation::startRequest(PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options)
334 RefPtr<GeoNotifier> notifier = GeoNotifier::create(this, successCallback, errorCallback, options);
336 // Check whether permissions have already been denied. Note that if this is the case,
337 // the permission state can not change again in the lifetime of this page.
339 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
340 else if (haveSuitableCachedPosition(notifier->m_options.get()))
341 notifier->setUseCachedPosition();
342 else if (notifier->hasZeroTimeout())
343 notifier->startTimerIfNeeded();
344 #if USE(PREEMPT_GEOLOCATION_PERMISSION)
345 else if (!isAllowed()) {
346 // if we don't yet have permission, request for permission before calling startUpdating()
347 m_pendingForPermissionNotifiers.add(notifier);
351 else if (startUpdating(notifier.get()))
352 notifier->startTimerIfNeeded();
354 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
356 return notifier.release();
359 void Geolocation::fatalErrorOccurred(Geolocation::GeoNotifier* notifier)
361 // This request has failed fatally. Remove it from our lists.
362 m_oneShots.remove(notifier);
363 m_watchers.remove(notifier);
369 void Geolocation::requestUsesCachedPosition(GeoNotifier* notifier)
371 // This is called asynchronously, so the permissions could have been denied
372 // since we last checked in startRequest.
374 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
378 m_requestsAwaitingCachedPosition.add(notifier);
380 // If permissions are allowed, make the callback
382 makeCachedPositionCallbacks();
386 // Request permissions, which may be synchronous or asynchronous.
390 void Geolocation::makeCachedPositionCallbacks()
392 // All modifications to m_requestsAwaitingCachedPosition are done
393 // asynchronously, so we don't need to worry about it being modified from
395 GeoNotifierSet::const_iterator end = m_requestsAwaitingCachedPosition.end();
396 for (GeoNotifierSet::const_iterator iter = m_requestsAwaitingCachedPosition.begin(); iter != end; ++iter) {
397 GeoNotifier* notifier = iter->get();
398 notifier->runSuccessCallback(m_cachedPosition.get());
400 // If this is a one-shot request, stop it. Otherwise, if the watch still
401 // exists, start the service to get updates.
402 if (m_oneShots.contains(notifier))
403 m_oneShots.remove(notifier);
404 else if (m_watchers.contains(notifier)) {
405 if (notifier->hasZeroTimeout() || startUpdating(notifier))
406 notifier->startTimerIfNeeded();
408 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
412 m_requestsAwaitingCachedPosition.clear();
418 void Geolocation::requestTimedOut(GeoNotifier* notifier)
420 // If this is a one-shot request, stop it.
421 m_oneShots.remove(notifier);
427 bool Geolocation::haveSuitableCachedPosition(PositionOptions* options)
429 if (!m_cachedPosition)
431 if (!options->hasMaximumAge())
433 if (!options->maximumAge())
435 DOMTimeStamp currentTimeMillis = convertSecondsToDOMTimeStamp(currentTime());
436 return m_cachedPosition->timestamp() > currentTimeMillis - options->maximumAge();
439 void Geolocation::clearWatch(int watchId)
441 if (watchId < firstAvailableWatchId)
444 #if USE(PREEMPT_GEOLOCATION_PERMISSION)
445 if (GeoNotifier* notifier = m_watchers.find(watchId))
446 m_pendingForPermissionNotifiers.remove(notifier);
448 m_watchers.remove(watchId);
454 void Geolocation::setIsAllowed(bool allowed)
456 // Protect the Geolocation object from garbage collection during a callback.
457 RefPtr<Geolocation> protect(this);
459 // This may be due to either a new position from the service, or a cached
461 m_allowGeolocation = allowed ? Yes : No;
463 #if USE(PREEMPT_GEOLOCATION_PERMISSION)
464 // Permission request was made during the startRequest process
465 if (!m_pendingForPermissionNotifiers.isEmpty()) {
466 handlePendingPermissionNotifiers();
467 m_pendingForPermissionNotifiers.clear();
473 RefPtr<PositionError> error = PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage);
474 error->setIsFatal(true);
475 handleError(error.get());
476 m_requestsAwaitingCachedPosition.clear();
480 // If the service has a last position, use it to call back for all requests.
481 // If any of the requests are waiting for permission for a cached position,
482 // the position from the service will be at least as fresh.
484 makeSuccessCallbacks();
486 makeCachedPositionCallbacks();
489 void Geolocation::sendError(GeoNotifierVector& notifiers, PositionError* error)
491 GeoNotifierVector::const_iterator end = notifiers.end();
492 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) {
493 RefPtr<GeoNotifier> notifier = *it;
495 if (notifier->m_errorCallback)
496 notifier->m_errorCallback->handleEvent(error);
500 void Geolocation::sendPosition(GeoNotifierVector& notifiers, Geoposition* position)
502 GeoNotifierVector::const_iterator end = notifiers.end();
503 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) {
504 RefPtr<GeoNotifier> notifier = *it;
505 ASSERT(notifier->m_successCallback);
507 notifier->m_successCallback->handleEvent(position);
511 void Geolocation::stopTimer(GeoNotifierVector& notifiers)
513 GeoNotifierVector::const_iterator end = notifiers.end();
514 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) {
515 RefPtr<GeoNotifier> notifier = *it;
516 notifier->m_timer.stop();
520 void Geolocation::stopTimersForOneShots()
522 GeoNotifierVector copy;
523 copyToVector(m_oneShots, copy);
528 void Geolocation::stopTimersForWatchers()
530 GeoNotifierVector copy;
531 m_watchers.getNotifiersVector(copy);
536 void Geolocation::stopTimers()
538 stopTimersForOneShots();
539 stopTimersForWatchers();
542 void Geolocation::cancelRequests(GeoNotifierVector& notifiers)
544 GeoNotifierVector::const_iterator end = notifiers.end();
545 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
546 (*it)->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, framelessDocumentErrorMessage));
549 void Geolocation::cancelAllRequests()
551 GeoNotifierVector copy;
552 copyToVector(m_oneShots, copy);
553 cancelRequests(copy);
554 m_watchers.getNotifiersVector(copy);
555 cancelRequests(copy);
558 void Geolocation::extractNotifiersWithCachedPosition(GeoNotifierVector& notifiers, GeoNotifierVector* cached)
560 GeoNotifierVector nonCached;
561 GeoNotifierVector::iterator end = notifiers.end();
562 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) {
563 GeoNotifier* notifier = it->get();
564 if (notifier->m_useCachedPosition) {
566 cached->append(notifier);
568 nonCached.append(notifier);
570 notifiers.swap(nonCached);
573 void Geolocation::copyToSet(const GeoNotifierVector& src, GeoNotifierSet& dest)
575 GeoNotifierVector::const_iterator end = src.end();
576 for (GeoNotifierVector::const_iterator it = src.begin(); it != end; ++it) {
577 GeoNotifier* notifier = it->get();
582 void Geolocation::handleError(PositionError* error)
586 GeoNotifierVector oneShotsCopy;
587 copyToVector(m_oneShots, oneShotsCopy);
589 GeoNotifierVector watchersCopy;
590 m_watchers.getNotifiersVector(watchersCopy);
592 // Clear the lists before we make the callbacks, to avoid clearing notifiers
593 // added by calls to Geolocation methods from the callbacks, and to prevent
594 // further callbacks to these notifiers.
595 GeoNotifierVector oneShotsWithCachedPosition;
597 if (error->isFatal())
600 // Don't send non-fatal errors to notifiers due to receive a cached position.
601 extractNotifiersWithCachedPosition(oneShotsCopy, &oneShotsWithCachedPosition);
602 extractNotifiersWithCachedPosition(watchersCopy, 0);
605 sendError(oneShotsCopy, error);
606 sendError(watchersCopy, error);
608 // hasListeners() doesn't distinguish between notifiers due to receive a
609 // cached position and those requiring a fresh position. Perform the check
610 // before restoring the notifiers below.
614 // Maintain a reference to the cached notifiers until their timer fires.
615 copyToSet(oneShotsWithCachedPosition, m_oneShots);
618 void Geolocation::requestPermission()
620 if (m_allowGeolocation > Unknown)
623 Page* page = this->page();
627 m_allowGeolocation = InProgress;
629 // Ask the embedder: it maintains the geolocation challenge policy itself.
630 #if ENABLE(CLIENT_BASED_GEOLOCATION)
631 page->geolocationController()->requestPermission(this);
633 page->chrome()->client()->requestGeolocationPermissionForFrame(frame(), this);
637 void Geolocation::positionChangedInternal()
639 m_cachedPosition = lastPosition();
641 // Stop all currently running timers.
645 // requestPermission() will ask the chrome for permission. This may be
646 // implemented synchronously or asynchronously. In both cases,
647 // makeSuccessCallbacks() will be called if permission is granted, so
648 // there's nothing more to do here.
653 makeSuccessCallbacks();
656 void Geolocation::makeSuccessCallbacks()
658 ASSERT(lastPosition());
661 GeoNotifierVector oneShotsCopy;
662 copyToVector(m_oneShots, oneShotsCopy);
664 GeoNotifierVector watchersCopy;
665 m_watchers.getNotifiersVector(watchersCopy);
667 // Clear the lists before we make the callbacks, to avoid clearing notifiers
668 // added by calls to Geolocation methods from the callbacks, and to prevent
669 // further callbacks to these notifiers.
672 sendPosition(oneShotsCopy, lastPosition());
673 sendPosition(watchersCopy, lastPosition());
679 #if ENABLE(CLIENT_BASED_GEOLOCATION)
681 void Geolocation::positionChanged()
683 positionChangedInternal();
686 void Geolocation::setError(GeolocationError* error)
688 RefPtr<PositionError> positionError = createPositionError(error);
689 handleError(positionError.get());
694 void Geolocation::geolocationServicePositionChanged(GeolocationService* service)
696 ASSERT_UNUSED(service, service == m_service);
697 ASSERT(m_service->lastPosition());
699 positionChangedInternal();
702 void Geolocation::geolocationServiceErrorOccurred(GeolocationService* service)
704 ASSERT(service->lastError());
706 // Note that we do not stop timers here. For one-shots, the request is
707 // cleared in handleError. For watchers, the spec requires that the timer is
709 handleError(service->lastError());
714 bool Geolocation::startUpdating(GeoNotifier* notifier)
716 #if ENABLE(CLIENT_BASED_GEOLOCATION)
717 Page* page = this->page();
721 page->geolocationController()->addObserver(this, notifier->m_options->enableHighAccuracy());
724 return m_service->startUpdating(notifier->m_options.get());
728 void Geolocation::stopUpdating()
730 #if ENABLE(CLIENT_BASED_GEOLOCATION)
731 Page* page = this->page();
735 page->geolocationController()->removeObserver(this);
737 m_service->stopUpdating();
742 #if USE(PREEMPT_GEOLOCATION_PERMISSION)
743 void Geolocation::handlePendingPermissionNotifiers()
745 // While we iterate through the list, we need not worry about list being modified as the permission
746 // is already set to Yes/No and no new listeners will be added to the pending list
747 GeoNotifierSet::const_iterator end = m_pendingForPermissionNotifiers.end();
748 for (GeoNotifierSet::const_iterator iter = m_pendingForPermissionNotifiers.begin(); iter != end; ++iter) {
749 GeoNotifier* notifier = iter->get();
752 // start all pending notification requests as permission granted.
753 // The notifier is always ref'ed by m_oneShots or m_watchers.
754 if (startUpdating(notifier))
755 notifier->startTimerIfNeeded();
757 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
759 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
764 } // namespace WebCore
770 void Geolocation::clearWatch(int) { }
771 void Geolocation::stop() { }
772 Geolocation::Geolocation(ScriptExecutionContext* context) : ActiveDOMObject(context, this) { }
773 Geolocation::~Geolocation() { }
774 void Geolocation::setIsAllowed(bool) { }
778 #endif // ENABLE(GEOLOCATION)