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 PassRefPtrWillBeRawPtr<Geoposition> createGeoposition(GeolocationPosition* position)
50 RefPtrWillBeRawPtr<Coordinates> coordinates = Coordinates::create(
52 position->longitude(),
53 position->canProvideAltitude(),
56 position->canProvideAltitudeAccuracy(),
57 position->altitudeAccuracy(),
58 position->canProvideHeading(),
60 position->canProvideSpeed(),
62 return Geoposition::create(coordinates.release(), convertSecondsToDOMTimeStamp(position->timestamp()));
65 static PassRefPtrWillBeRawPtr<PositionError> createPositionError(GeolocationError* error)
67 PositionError::ErrorCode code = PositionError::POSITION_UNAVAILABLE;
68 switch (error->code()) {
69 case GeolocationError::PermissionDenied:
70 code = PositionError::PERMISSION_DENIED;
72 case GeolocationError::PositionUnavailable:
73 code = PositionError::POSITION_UNAVAILABLE;
77 return PositionError::create(code, error->message());
80 DEFINE_GC_INFO(Geolocation::GeoNotifier);
82 Geolocation::GeoNotifier::GeoNotifier(Geolocation* geolocation, PassOwnPtr<PositionCallback> successCallback, PassOwnPtr<PositionErrorCallback> errorCallback, PassRefPtrWillBeRawPtr<PositionOptions> options)
83 : m_geolocation(geolocation)
84 , m_successCallback(successCallback)
85 , m_errorCallback(errorCallback)
87 , m_timer(this, &Geolocation::GeoNotifier::timerFired)
88 , m_useCachedPosition(false)
90 ASSERT(m_geolocation);
91 ASSERT(m_successCallback);
92 // If no options were supplied from JS, we should have created a default set
93 // of options in JSGeolocationCustom.cpp.
97 void Geolocation::GeoNotifier::trace(Visitor* visitor)
99 visitor->trace(m_geolocation);
100 visitor->trace(m_options);
101 visitor->trace(m_fatalError);
104 void Geolocation::GeoNotifier::setFatalError(PassRefPtrWillBeRawPtr<PositionError> error)
106 // If a fatal error has already been set, stick with it. This makes sure that
107 // when permission is denied, this is the error reported, as required by the
112 m_fatalError = error;
113 // An existing timer may not have a zero timeout.
115 m_timer.startOneShot(0);
118 void Geolocation::GeoNotifier::setUseCachedPosition()
120 m_useCachedPosition = true;
121 m_timer.startOneShot(0);
124 bool Geolocation::GeoNotifier::hasZeroTimeout() const
126 return m_options->hasTimeout() && m_options->timeout() == 0;
129 void Geolocation::GeoNotifier::runSuccessCallback(Geoposition* position)
131 // If we are here and the Geolocation permission is not approved, something has
132 // gone horribly wrong.
133 if (!m_geolocation->isAllowed())
136 m_successCallback->handleEvent(position);
139 void Geolocation::GeoNotifier::runErrorCallback(PositionError* error)
142 m_errorCallback->handleEvent(error);
145 void Geolocation::GeoNotifier::startTimerIfNeeded()
147 if (m_options->hasTimeout())
148 m_timer.startOneShot(m_options->timeout() / 1000.0);
151 void Geolocation::GeoNotifier::stopTimer()
156 void Geolocation::GeoNotifier::timerFired(Timer<GeoNotifier>*)
160 // Protect this GeoNotifier object, since it
161 // could be deleted by a call to clearWatch in a callback.
162 RefPtrWillBeRawPtr<GeoNotifier> protect(this);
164 // Test for fatal error first. This is required for the case where the Frame is
165 // disconnected and requests are cancelled.
167 runErrorCallback(m_fatalError.get());
168 // This will cause this notifier to be deleted.
169 m_geolocation->fatalErrorOccurred(this);
173 if (m_useCachedPosition) {
174 // Clear the cached position flag in case this is a watch request, which
175 // will continue to run.
176 m_useCachedPosition = false;
177 m_geolocation->requestUsesCachedPosition(this);
181 if (m_errorCallback) {
182 RefPtrWillBeRawPtr<PositionError> error = PositionError::create(PositionError::TIMEOUT, "Timeout expired");
183 m_errorCallback->handleEvent(error.get());
185 m_geolocation->requestTimedOut(this);
188 void Geolocation::Watchers::trace(Visitor* visitor)
190 visitor->trace(m_idToNotifierMap);
191 visitor->trace(m_notifierToIdMap);
194 bool Geolocation::Watchers::add(int id, PassRefPtrWillBeRawPtr<GeoNotifier> prpNotifier)
197 RefPtrWillBeRawPtr<GeoNotifier> notifier = prpNotifier;
199 if (!m_idToNotifierMap.add(id, notifier.get()).isNewEntry)
201 m_notifierToIdMap.set(notifier.release(), id);
205 Geolocation::GeoNotifier* Geolocation::Watchers::find(int id)
208 IdToNotifierMap::iterator iter = m_idToNotifierMap.find(id);
209 if (iter == m_idToNotifierMap.end())
211 return iter->value.get();
214 void Geolocation::Watchers::remove(int id)
217 IdToNotifierMap::iterator iter = m_idToNotifierMap.find(id);
218 if (iter == m_idToNotifierMap.end())
220 m_notifierToIdMap.remove(iter->value);
221 m_idToNotifierMap.remove(iter);
224 void Geolocation::Watchers::remove(GeoNotifier* notifier)
226 NotifierToIdMap::iterator iter = m_notifierToIdMap.find(notifier);
227 if (iter == m_notifierToIdMap.end())
229 m_idToNotifierMap.remove(iter->value);
230 m_notifierToIdMap.remove(iter);
233 bool Geolocation::Watchers::contains(GeoNotifier* notifier) const
235 return m_notifierToIdMap.contains(notifier);
238 void Geolocation::Watchers::clear()
240 m_idToNotifierMap.clear();
241 m_notifierToIdMap.clear();
244 bool Geolocation::Watchers::isEmpty() const
246 return m_idToNotifierMap.isEmpty();
249 void Geolocation::Watchers::getNotifiersVector(GeoNotifierVector& copy) const
251 copyValuesToVector(m_idToNotifierMap, copy);
254 DEFINE_GC_INFO(Geolocation);
256 PassRefPtrWillBeRawPtr<Geolocation> Geolocation::create(ExecutionContext* context)
258 RefPtrWillBeRawPtr<Geolocation> geolocation = adoptRefWillBeNoop(new Geolocation(context));
259 geolocation->suspendIfNeeded();
260 return geolocation.release();
263 Geolocation::Geolocation(ExecutionContext* context)
264 : ActiveDOMObject(context)
265 , m_allowGeolocation(Unknown)
267 ScriptWrappable::init(this);
270 Geolocation::~Geolocation()
272 ASSERT(m_allowGeolocation != InProgress);
275 void Geolocation::trace(Visitor* visitor)
277 visitor->trace(m_oneShots);
278 visitor->trace(m_watchers);
279 visitor->trace(m_pendingForPermissionNotifiers);
280 visitor->trace(m_lastPosition);
281 visitor->trace(m_requestsAwaitingCachedPosition);
284 Document* Geolocation::document() const
286 return toDocument(executionContext());
289 Frame* Geolocation::frame() const
291 return document() ? document()->frame() : 0;
294 Page* Geolocation::page() const
296 return document() ? document()->page() : 0;
299 void Geolocation::stop()
301 Page* page = this->page();
302 if (page && m_allowGeolocation == InProgress)
303 GeolocationController::from(page)->cancelPermissionRequest(this);
304 // The frame may be moving to a new page and we want to get the permissions from the new page's client.
305 m_allowGeolocation = Unknown;
308 m_pendingForPermissionNotifiers.clear();
311 Geoposition* Geolocation::lastPosition()
313 Page* page = this->page();
317 m_lastPosition = createGeoposition(GeolocationController::from(page)->lastPosition());
319 return m_lastPosition.get();
322 void Geolocation::getCurrentPosition(PassOwnPtr<PositionCallback> successCallback, PassOwnPtr<PositionErrorCallback> errorCallback, PassRefPtrWillBeRawPtr<PositionOptions> options)
327 RefPtrWillBeRawPtr<GeoNotifier> notifier = GeoNotifier::create(this, successCallback, errorCallback, options);
328 startRequest(notifier.get());
330 m_oneShots.add(notifier);
333 int Geolocation::watchPosition(PassOwnPtr<PositionCallback> successCallback, PassOwnPtr<PositionErrorCallback> errorCallback, PassRefPtrWillBeRawPtr<PositionOptions> options)
338 RefPtrWillBeRawPtr<GeoNotifier> notifier = GeoNotifier::create(this, successCallback, errorCallback, options);
339 startRequest(notifier.get());
342 // Keep asking for the next id until we're given one that we don't already have.
344 watchID = executionContext()->circularSequentialID();
345 } while (!m_watchers.add(watchID, notifier));
349 void Geolocation::startRequest(GeoNotifier *notifier)
351 // Check whether permissions have already been denied. Note that if this is the case,
352 // the permission state can not change again in the lifetime of this page.
354 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
355 else if (haveSuitableCachedPosition(notifier->options()))
356 notifier->setUseCachedPosition();
357 else if (notifier->hasZeroTimeout())
358 notifier->startTimerIfNeeded();
359 else if (!isAllowed()) {
360 // if we don't yet have permission, request for permission before calling startUpdating()
361 m_pendingForPermissionNotifiers.add(notifier);
363 } else if (startUpdating(notifier))
364 notifier->startTimerIfNeeded();
366 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
369 void Geolocation::fatalErrorOccurred(Geolocation::GeoNotifier* notifier)
371 // This request has failed fatally. Remove it from our lists.
372 m_oneShots.remove(notifier);
373 m_watchers.remove(notifier);
379 void Geolocation::requestUsesCachedPosition(GeoNotifier* notifier)
381 // This is called asynchronously, so the permissions could have been denied
382 // since we last checked in startRequest.
384 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
388 m_requestsAwaitingCachedPosition.add(notifier);
390 // If permissions are allowed, make the callback
392 makeCachedPositionCallbacks();
396 // Request permissions, which may be synchronous or asynchronous.
400 void Geolocation::makeCachedPositionCallbacks()
402 // All modifications to m_requestsAwaitingCachedPosition are done
403 // asynchronously, so we don't need to worry about it being modified from
405 GeoNotifierSet::const_iterator end = m_requestsAwaitingCachedPosition.end();
406 for (GeoNotifierSet::const_iterator iter = m_requestsAwaitingCachedPosition.begin(); iter != end; ++iter) {
407 GeoNotifier* notifier = iter->get();
408 notifier->runSuccessCallback(lastPosition());
410 // If this is a one-shot request, stop it. Otherwise, if the watch still
411 // exists, start the service to get updates.
412 if (m_oneShots.contains(notifier))
413 m_oneShots.remove(notifier);
414 else if (m_watchers.contains(notifier)) {
415 if (notifier->hasZeroTimeout() || startUpdating(notifier))
416 notifier->startTimerIfNeeded();
418 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
422 m_requestsAwaitingCachedPosition.clear();
428 void Geolocation::requestTimedOut(GeoNotifier* notifier)
430 // If this is a one-shot request, stop it.
431 m_oneShots.remove(notifier);
437 bool Geolocation::haveSuitableCachedPosition(PositionOptions* options)
439 Geoposition* cachedPosition = lastPosition();
442 if (!options->hasMaximumAge())
444 if (!options->maximumAge())
446 DOMTimeStamp currentTimeMillis = convertSecondsToDOMTimeStamp(currentTime());
447 return cachedPosition->timestamp() > currentTimeMillis - options->maximumAge();
450 void Geolocation::clearWatch(int watchID)
455 if (GeoNotifier* notifier = m_watchers.find(watchID))
456 m_pendingForPermissionNotifiers.remove(notifier);
457 m_watchers.remove(watchID);
463 void Geolocation::setIsAllowed(bool allowed)
465 // Protect the Geolocation object from garbage collection during a callback.
466 RefPtrWillBeRawPtr<Geolocation> protect(this);
468 // This may be due to either a new position from the service, or a cached
470 m_allowGeolocation = allowed ? Yes : No;
472 // Permission request was made during the startRequest process
473 if (!m_pendingForPermissionNotifiers.isEmpty()) {
474 handlePendingPermissionNotifiers();
475 m_pendingForPermissionNotifiers.clear();
480 RefPtrWillBeRawPtr<PositionError> error = PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage);
481 error->setIsFatal(true);
482 handleError(error.get());
483 m_requestsAwaitingCachedPosition.clear();
487 // If the service has a last position, use it to call back for all requests.
488 // If any of the requests are waiting for permission for a cached position,
489 // the position from the service will be at least as fresh.
491 makeSuccessCallbacks();
493 makeCachedPositionCallbacks();
496 void Geolocation::sendError(GeoNotifierVector& notifiers, PositionError* error)
498 GeoNotifierVector::const_iterator end = notifiers.end();
499 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
500 (*it)->runErrorCallback(error);
503 void Geolocation::sendPosition(GeoNotifierVector& notifiers, Geoposition* position)
505 GeoNotifierVector::const_iterator end = notifiers.end();
506 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
507 (*it)->runSuccessCallback(position);
510 void Geolocation::stopTimer(GeoNotifierVector& notifiers)
512 GeoNotifierVector::const_iterator end = notifiers.end();
513 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
517 void Geolocation::stopTimersForOneShots()
519 GeoNotifierVector copy;
520 copyToVector(m_oneShots, copy);
525 void Geolocation::stopTimersForWatchers()
527 GeoNotifierVector copy;
528 m_watchers.getNotifiersVector(copy);
533 void Geolocation::stopTimers()
535 stopTimersForOneShots();
536 stopTimersForWatchers();
539 void Geolocation::cancelRequests(GeoNotifierVector& notifiers)
541 GeoNotifierVector::const_iterator end = notifiers.end();
542 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
543 (*it)->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, framelessDocumentErrorMessage));
546 void Geolocation::cancelAllRequests()
548 GeoNotifierVector copy;
549 copyToVector(m_oneShots, copy);
550 cancelRequests(copy);
551 m_watchers.getNotifiersVector(copy);
552 cancelRequests(copy);
555 void Geolocation::extractNotifiersWithCachedPosition(GeoNotifierVector& notifiers, GeoNotifierVector* cached)
557 GeoNotifierVector nonCached;
558 GeoNotifierVector::iterator end = notifiers.end();
559 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) {
560 GeoNotifier* notifier = it->get();
561 if (notifier->useCachedPosition()) {
563 cached->append(notifier);
565 nonCached.append(notifier);
567 notifiers.swap(nonCached);
570 void Geolocation::copyToSet(const GeoNotifierVector& src, GeoNotifierSet& dest)
572 GeoNotifierVector::const_iterator end = src.end();
573 for (GeoNotifierVector::const_iterator it = src.begin(); it != end; ++it) {
574 GeoNotifier* notifier = it->get();
579 void Geolocation::handleError(PositionError* error)
583 GeoNotifierVector oneShotsCopy;
584 copyToVector(m_oneShots, oneShotsCopy);
586 GeoNotifierVector watchersCopy;
587 m_watchers.getNotifiersVector(watchersCopy);
589 // Clear the lists before we make the callbacks, to avoid clearing notifiers
590 // added by calls to Geolocation methods from the callbacks, and to prevent
591 // further callbacks to these notifiers.
592 GeoNotifierVector oneShotsWithCachedPosition;
594 if (error->isFatal())
597 // Don't send non-fatal errors to notifiers due to receive a cached position.
598 extractNotifiersWithCachedPosition(oneShotsCopy, &oneShotsWithCachedPosition);
599 extractNotifiersWithCachedPosition(watchersCopy, 0);
602 sendError(oneShotsCopy, error);
603 sendError(watchersCopy, error);
605 // hasListeners() doesn't distinguish between notifiers due to receive a
606 // cached position and those requiring a fresh position. Perform the check
607 // before restoring the notifiers below.
611 // Maintain a reference to the cached notifiers until their timer fires.
612 copyToSet(oneShotsWithCachedPosition, m_oneShots);
615 void Geolocation::requestPermission()
617 if (m_allowGeolocation > Unknown)
620 Page* page = this->page();
624 m_allowGeolocation = InProgress;
626 // Ask the embedder: it maintains the geolocation challenge policy itself.
627 GeolocationController::from(page)->requestPermission(this);
630 void Geolocation::makeSuccessCallbacks()
632 ASSERT(lastPosition());
635 GeoNotifierVector oneShotsCopy;
636 copyToVector(m_oneShots, oneShotsCopy);
638 GeoNotifierVector watchersCopy;
639 m_watchers.getNotifiersVector(watchersCopy);
641 // Clear the lists before we make the callbacks, to avoid clearing notifiers
642 // added by calls to Geolocation methods from the callbacks, and to prevent
643 // further callbacks to these notifiers.
646 // Also clear the set of notifiers waiting for a cached position. All the
647 // oneshots and watchers will receive a position now, and if they happen to
648 // be lingering in that set, avoid this bug: http://crbug.com/311876 .
649 m_requestsAwaitingCachedPosition.clear();
651 sendPosition(oneShotsCopy, lastPosition());
652 sendPosition(watchersCopy, lastPosition());
658 void Geolocation::positionChanged()
662 // Stop all currently running timers.
665 makeSuccessCallbacks();
668 void Geolocation::setError(GeolocationError* error)
670 RefPtrWillBeRawPtr<PositionError> positionError = createPositionError(error);
671 handleError(positionError.get());
674 bool Geolocation::startUpdating(GeoNotifier* notifier)
676 Page* page = this->page();
680 GeolocationController::from(page)->addObserver(this, notifier->options()->enableHighAccuracy());
684 void Geolocation::stopUpdating()
686 Page* page = this->page();
690 GeolocationController::from(page)->removeObserver(this);
693 void Geolocation::handlePendingPermissionNotifiers()
695 // While we iterate through the list, we need not worry about list being modified as the permission
696 // is already set to Yes/No and no new listeners will be added to the pending list
697 GeoNotifierSet::const_iterator end = m_pendingForPermissionNotifiers.end();
698 for (GeoNotifierSet::const_iterator iter = m_pendingForPermissionNotifiers.begin(); iter != end; ++iter) {
699 GeoNotifier* notifier = iter->get();
702 // start all pending notification requests as permission granted.
703 // The notifier is always ref'ed by m_oneShots or m_watchers.
704 if (startUpdating(notifier))
705 notifier->startTimerIfNeeded();
707 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
709 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
714 } // namespace WebCore