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 "modules/geolocation/Geolocation.h"
31 #include "core/dom/Document.h"
32 #include "wtf/CurrentTime.h"
34 #include "modules/geolocation/Coordinates.h"
35 #include "modules/geolocation/GeolocationController.h"
36 #include "modules/geolocation/GeolocationError.h"
37 #include "modules/geolocation/GeolocationPosition.h"
41 static const char permissionDeniedErrorMessage[] = "User denied Geolocation";
42 static const char failedToStartServiceErrorMessage[] = "Failed to start Geolocation service";
43 static const char framelessDocumentErrorMessage[] = "Geolocation cannot be used in frameless documents";
45 static PassRefPtr<Geoposition> createGeoposition(GeolocationPosition* position)
50 RefPtr<Coordinates> coordinates = Coordinates::create(position->latitude(), position->longitude(), position->canProvideAltitude(), position->altitude(),
51 position->accuracy(), position->canProvideAltitudeAccuracy(), position->altitudeAccuracy(),
52 position->canProvideHeading(), position->heading(), position->canProvideSpeed(), position->speed());
53 return Geoposition::create(coordinates.release(), convertSecondsToDOMTimeStamp(position->timestamp()));
56 static PassRefPtr<PositionError> createPositionError(GeolocationError* error)
58 PositionError::ErrorCode code = PositionError::POSITION_UNAVAILABLE;
59 switch (error->code()) {
60 case GeolocationError::PermissionDenied:
61 code = PositionError::PERMISSION_DENIED;
63 case GeolocationError::PositionUnavailable:
64 code = PositionError::POSITION_UNAVAILABLE;
68 return PositionError::create(code, error->message());
71 Geolocation::GeoNotifier::GeoNotifier(Geolocation* geolocation, PassOwnPtr<PositionCallback> successCallback, PassOwnPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options)
72 : m_geolocation(geolocation)
73 , m_successCallback(successCallback)
74 , m_errorCallback(errorCallback)
76 , m_timer(this, &Geolocation::GeoNotifier::timerFired)
77 , m_useCachedPosition(false)
79 ASSERT(m_geolocation);
80 ASSERT(m_successCallback);
81 // If no options were supplied from JS, we should have created a default set
82 // of options in JSGeolocationCustom.cpp.
86 void Geolocation::GeoNotifier::setFatalError(PassRefPtr<PositionError> error)
88 // If a fatal error has already been set, stick with it. This makes sure that
89 // when permission is denied, this is the error reported, as required by the
95 // An existing timer may not have a zero timeout.
97 m_timer.startOneShot(0);
100 void Geolocation::GeoNotifier::setUseCachedPosition()
102 m_useCachedPosition = true;
103 m_timer.startOneShot(0);
106 bool Geolocation::GeoNotifier::hasZeroTimeout() const
108 return m_options->hasTimeout() && m_options->timeout() == 0;
111 void Geolocation::GeoNotifier::runSuccessCallback(Geoposition* position)
113 // If we are here and the Geolocation permission is not approved, something has
114 // gone horribly wrong.
115 if (!m_geolocation->isAllowed())
118 m_successCallback->handleEvent(position);
121 void Geolocation::GeoNotifier::runErrorCallback(PositionError* error)
124 m_errorCallback->handleEvent(error);
127 void Geolocation::GeoNotifier::startTimerIfNeeded()
129 if (m_options->hasTimeout())
130 m_timer.startOneShot(m_options->timeout() / 1000.0);
133 void Geolocation::GeoNotifier::stopTimer()
138 void Geolocation::GeoNotifier::timerFired(Timer<GeoNotifier>*)
142 // Protect this GeoNotifier object, since it
143 // could be deleted by a call to clearWatch in a callback.
144 RefPtr<GeoNotifier> protect(this);
146 // Test for fatal error first. This is required for the case where the Frame is
147 // disconnected and requests are cancelled.
149 runErrorCallback(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 bool Geolocation::Watchers::add(int id, PassRefPtr<GeoNotifier> prpNotifier)
173 RefPtr<GeoNotifier> notifier = prpNotifier;
175 if (!m_idToNotifierMap.add(id, notifier.get()).isNewEntry)
177 m_notifierToIdMap.set(notifier.release(), id);
181 Geolocation::GeoNotifier* Geolocation::Watchers::find(int id)
184 IdToNotifierMap::iterator iter = m_idToNotifierMap.find(id);
185 if (iter == m_idToNotifierMap.end())
187 return iter->value.get();
190 void Geolocation::Watchers::remove(int id)
193 IdToNotifierMap::iterator iter = m_idToNotifierMap.find(id);
194 if (iter == m_idToNotifierMap.end())
196 m_notifierToIdMap.remove(iter->value);
197 m_idToNotifierMap.remove(iter);
200 void Geolocation::Watchers::remove(GeoNotifier* notifier)
202 NotifierToIdMap::iterator iter = m_notifierToIdMap.find(notifier);
203 if (iter == m_notifierToIdMap.end())
205 m_idToNotifierMap.remove(iter->value);
206 m_notifierToIdMap.remove(iter);
209 bool Geolocation::Watchers::contains(GeoNotifier* notifier) const
211 return m_notifierToIdMap.contains(notifier);
214 void Geolocation::Watchers::clear()
216 m_idToNotifierMap.clear();
217 m_notifierToIdMap.clear();
220 bool Geolocation::Watchers::isEmpty() const
222 return m_idToNotifierMap.isEmpty();
225 void Geolocation::Watchers::getNotifiersVector(GeoNotifierVector& copy) const
227 copyValuesToVector(m_idToNotifierMap, copy);
230 PassRefPtr<Geolocation> Geolocation::create(ExecutionContext* context)
232 RefPtr<Geolocation> geolocation = adoptRef(new Geolocation(context));
233 geolocation->suspendIfNeeded();
234 return geolocation.release();
237 Geolocation::Geolocation(ExecutionContext* context)
238 : ActiveDOMObject(context)
239 , m_allowGeolocation(Unknown)
241 ScriptWrappable::init(this);
244 Geolocation::~Geolocation()
246 ASSERT(m_allowGeolocation != InProgress);
249 Document* Geolocation::document() const
251 return toDocument(executionContext());
254 Frame* Geolocation::frame() const
256 return document() ? document()->frame() : 0;
259 Page* Geolocation::page() const
261 return document() ? document()->page() : 0;
264 void Geolocation::stop()
266 Page* page = this->page();
267 if (page && m_allowGeolocation == InProgress)
268 GeolocationController::from(page)->cancelPermissionRequest(this);
269 // The frame may be moving to a new page and we want to get the permissions from the new page's client.
270 m_allowGeolocation = Unknown;
273 m_pendingForPermissionNotifiers.clear();
276 Geoposition* Geolocation::lastPosition()
278 Page* page = this->page();
282 m_lastPosition = createGeoposition(GeolocationController::from(page)->lastPosition());
284 return m_lastPosition.get();
287 void Geolocation::getCurrentPosition(PassOwnPtr<PositionCallback> successCallback, PassOwnPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options)
292 RefPtr<GeoNotifier> notifier = GeoNotifier::create(this, successCallback, errorCallback, options);
293 startRequest(notifier.get());
295 m_oneShots.add(notifier);
298 int Geolocation::watchPosition(PassOwnPtr<PositionCallback> successCallback, PassOwnPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options)
303 RefPtr<GeoNotifier> notifier = GeoNotifier::create(this, successCallback, errorCallback, options);
304 startRequest(notifier.get());
307 // Keep asking for the next id until we're given one that we don't already have.
309 watchID = executionContext()->circularSequentialID();
310 } while (!m_watchers.add(watchID, notifier));
314 void Geolocation::startRequest(GeoNotifier *notifier)
316 // Check whether permissions have already been denied. Note that if this is the case,
317 // the permission state can not change again in the lifetime of this page.
319 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
320 else if (haveSuitableCachedPosition(notifier->options()))
321 notifier->setUseCachedPosition();
322 else if (notifier->hasZeroTimeout())
323 notifier->startTimerIfNeeded();
324 else if (!isAllowed()) {
325 // if we don't yet have permission, request for permission before calling startUpdating()
326 m_pendingForPermissionNotifiers.add(notifier);
328 } else if (startUpdating(notifier))
329 notifier->startTimerIfNeeded();
331 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
334 void Geolocation::fatalErrorOccurred(Geolocation::GeoNotifier* notifier)
336 // This request has failed fatally. Remove it from our lists.
337 m_oneShots.remove(notifier);
338 m_watchers.remove(notifier);
344 void Geolocation::requestUsesCachedPosition(GeoNotifier* notifier)
346 // This is called asynchronously, so the permissions could have been denied
347 // since we last checked in startRequest.
349 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
353 m_requestsAwaitingCachedPosition.add(notifier);
355 // If permissions are allowed, make the callback
357 makeCachedPositionCallbacks();
361 // Request permissions, which may be synchronous or asynchronous.
365 void Geolocation::makeCachedPositionCallbacks()
367 // All modifications to m_requestsAwaitingCachedPosition are done
368 // asynchronously, so we don't need to worry about it being modified from
370 GeoNotifierSet::const_iterator end = m_requestsAwaitingCachedPosition.end();
371 for (GeoNotifierSet::const_iterator iter = m_requestsAwaitingCachedPosition.begin(); iter != end; ++iter) {
372 GeoNotifier* notifier = iter->get();
373 notifier->runSuccessCallback(lastPosition());
375 // If this is a one-shot request, stop it. Otherwise, if the watch still
376 // exists, start the service to get updates.
377 if (m_oneShots.contains(notifier))
378 m_oneShots.remove(notifier);
379 else if (m_watchers.contains(notifier)) {
380 if (notifier->hasZeroTimeout() || startUpdating(notifier))
381 notifier->startTimerIfNeeded();
383 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
387 m_requestsAwaitingCachedPosition.clear();
393 void Geolocation::requestTimedOut(GeoNotifier* notifier)
395 // If this is a one-shot request, stop it.
396 m_oneShots.remove(notifier);
402 bool Geolocation::haveSuitableCachedPosition(PositionOptions* options)
404 Geoposition* cachedPosition = lastPosition();
407 if (!options->hasMaximumAge())
409 if (!options->maximumAge())
411 DOMTimeStamp currentTimeMillis = convertSecondsToDOMTimeStamp(currentTime());
412 return cachedPosition->timestamp() > currentTimeMillis - options->maximumAge();
415 void Geolocation::clearWatch(int watchID)
420 if (GeoNotifier* notifier = m_watchers.find(watchID))
421 m_pendingForPermissionNotifiers.remove(notifier);
422 m_watchers.remove(watchID);
428 void Geolocation::setIsAllowed(bool allowed)
430 // Protect the Geolocation object from garbage collection during a callback.
431 RefPtr<Geolocation> protect(this);
433 // This may be due to either a new position from the service, or a cached
435 m_allowGeolocation = allowed ? Yes : No;
437 // Permission request was made during the startRequest process
438 if (!m_pendingForPermissionNotifiers.isEmpty()) {
439 handlePendingPermissionNotifiers();
440 m_pendingForPermissionNotifiers.clear();
445 RefPtr<PositionError> error = PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage);
446 error->setIsFatal(true);
447 handleError(error.get());
448 m_requestsAwaitingCachedPosition.clear();
452 // If the service has a last position, use it to call back for all requests.
453 // If any of the requests are waiting for permission for a cached position,
454 // the position from the service will be at least as fresh.
456 makeSuccessCallbacks();
458 makeCachedPositionCallbacks();
461 void Geolocation::sendError(GeoNotifierVector& notifiers, PositionError* error)
463 GeoNotifierVector::const_iterator end = notifiers.end();
464 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) {
465 RefPtr<GeoNotifier> notifier = *it;
467 notifier->runErrorCallback(error);
471 void Geolocation::sendPosition(GeoNotifierVector& notifiers, Geoposition* position)
473 GeoNotifierVector::const_iterator end = notifiers.end();
474 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
475 (*it)->runSuccessCallback(position);
478 void Geolocation::stopTimer(GeoNotifierVector& notifiers)
480 GeoNotifierVector::const_iterator end = notifiers.end();
481 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
485 void Geolocation::stopTimersForOneShots()
487 GeoNotifierVector copy;
488 copyToVector(m_oneShots, copy);
493 void Geolocation::stopTimersForWatchers()
495 GeoNotifierVector copy;
496 m_watchers.getNotifiersVector(copy);
501 void Geolocation::stopTimers()
503 stopTimersForOneShots();
504 stopTimersForWatchers();
507 void Geolocation::cancelRequests(GeoNotifierVector& notifiers)
509 GeoNotifierVector::const_iterator end = notifiers.end();
510 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
511 (*it)->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, framelessDocumentErrorMessage));
514 void Geolocation::cancelAllRequests()
516 GeoNotifierVector copy;
517 copyToVector(m_oneShots, copy);
518 cancelRequests(copy);
519 m_watchers.getNotifiersVector(copy);
520 cancelRequests(copy);
523 void Geolocation::extractNotifiersWithCachedPosition(GeoNotifierVector& notifiers, GeoNotifierVector* cached)
525 GeoNotifierVector nonCached;
526 GeoNotifierVector::iterator end = notifiers.end();
527 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) {
528 GeoNotifier* notifier = it->get();
529 if (notifier->useCachedPosition()) {
531 cached->append(notifier);
533 nonCached.append(notifier);
535 notifiers.swap(nonCached);
538 void Geolocation::copyToSet(const GeoNotifierVector& src, GeoNotifierSet& dest)
540 GeoNotifierVector::const_iterator end = src.end();
541 for (GeoNotifierVector::const_iterator it = src.begin(); it != end; ++it) {
542 GeoNotifier* notifier = it->get();
547 void Geolocation::handleError(PositionError* error)
551 GeoNotifierVector oneShotsCopy;
552 copyToVector(m_oneShots, oneShotsCopy);
554 GeoNotifierVector watchersCopy;
555 m_watchers.getNotifiersVector(watchersCopy);
557 // Clear the lists before we make the callbacks, to avoid clearing notifiers
558 // added by calls to Geolocation methods from the callbacks, and to prevent
559 // further callbacks to these notifiers.
560 GeoNotifierVector oneShotsWithCachedPosition;
562 if (error->isFatal())
565 // Don't send non-fatal errors to notifiers due to receive a cached position.
566 extractNotifiersWithCachedPosition(oneShotsCopy, &oneShotsWithCachedPosition);
567 extractNotifiersWithCachedPosition(watchersCopy, 0);
570 sendError(oneShotsCopy, error);
571 sendError(watchersCopy, error);
573 // hasListeners() doesn't distinguish between notifiers due to receive a
574 // cached position and those requiring a fresh position. Perform the check
575 // before restoring the notifiers below.
579 // Maintain a reference to the cached notifiers until their timer fires.
580 copyToSet(oneShotsWithCachedPosition, m_oneShots);
583 void Geolocation::requestPermission()
585 if (m_allowGeolocation > Unknown)
588 Page* page = this->page();
592 m_allowGeolocation = InProgress;
594 // Ask the embedder: it maintains the geolocation challenge policy itself.
595 GeolocationController::from(page)->requestPermission(this);
598 void Geolocation::makeSuccessCallbacks()
600 ASSERT(lastPosition());
603 GeoNotifierVector oneShotsCopy;
604 copyToVector(m_oneShots, oneShotsCopy);
606 GeoNotifierVector watchersCopy;
607 m_watchers.getNotifiersVector(watchersCopy);
609 // Clear the lists before we make the callbacks, to avoid clearing notifiers
610 // added by calls to Geolocation methods from the callbacks, and to prevent
611 // further callbacks to these notifiers.
614 // Also clear the set of notifiers waiting for a cached position. All the
615 // oneshots and watchers will receive a position now, and if they happen to
616 // be lingering in that set, avoid this bug: http://crbug.com/311876 .
617 m_requestsAwaitingCachedPosition.clear();
619 sendPosition(oneShotsCopy, lastPosition());
620 sendPosition(watchersCopy, lastPosition());
626 void Geolocation::positionChanged()
630 // Stop all currently running timers.
633 makeSuccessCallbacks();
636 void Geolocation::setError(GeolocationError* error)
638 RefPtr<PositionError> positionError = createPositionError(error);
639 handleError(positionError.get());
642 bool Geolocation::startUpdating(GeoNotifier* notifier)
644 Page* page = this->page();
648 GeolocationController::from(page)->addObserver(this, notifier->options()->enableHighAccuracy());
652 void Geolocation::stopUpdating()
654 Page* page = this->page();
658 GeolocationController::from(page)->removeObserver(this);
661 void Geolocation::handlePendingPermissionNotifiers()
663 // While we iterate through the list, we need not worry about list being modified as the permission
664 // is already set to Yes/No and no new listeners will be added to the pending list
665 GeoNotifierSet::const_iterator end = m_pendingForPermissionNotifiers.end();
666 for (GeoNotifierSet::const_iterator iter = m_pendingForPermissionNotifiers.begin(); iter != end; ++iter) {
667 GeoNotifier* notifier = iter->get();
670 // start all pending notification requests as permission granted.
671 // The notifier is always ref'ed by m_oneShots or m_watchers.
672 if (startUpdating(notifier))
673 notifier->startTimerIfNeeded();
675 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
677 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
682 } // namespace WebCore