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)
33 #include "Coordinates.h"
36 #include "GeoNotifier.h"
37 #include "GeolocationController.h"
38 #include "GeolocationError.h"
39 #include "GeolocationPosition.h"
40 #include "Geoposition.h"
42 #include "PositionError.h"
43 #include "SecurityOrigin.h"
44 #include <wtf/CurrentTime.h>
46 #include <wtf/text/StringBuilder.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";
53 static const char originCannotRequestGeolocationErrorMessage[] = "Origin does not have permission to use Geolocation service";
55 static RefPtr<Geoposition> createGeoposition(GeolocationPosition* position)
60 RefPtr<Coordinates> coordinates = Coordinates::create(position->latitude(), position->longitude(), position->canProvideAltitude(), position->altitude(),
61 position->accuracy(), position->canProvideAltitudeAccuracy(), position->altitudeAccuracy(),
62 position->canProvideHeading(), position->heading(), position->canProvideSpeed(), position->speed());
63 return Geoposition::create(WTFMove(coordinates), convertSecondsToDOMTimeStamp(position->timestamp()));
66 static Ref<PositionError> createPositionError(GeolocationError* error)
68 PositionError::ErrorCode code = PositionError::POSITION_UNAVAILABLE;
69 switch (error->code()) {
70 case GeolocationError::PermissionDenied:
71 code = PositionError::PERMISSION_DENIED;
73 case GeolocationError::PositionUnavailable:
74 code = PositionError::POSITION_UNAVAILABLE;
78 return PositionError::create(code, error->message());
81 bool Geolocation::Watchers::add(int id, RefPtr<GeoNotifier>&& notifier)
85 if (!m_idToNotifierMap.add(id, notifier.get()).isNewEntry)
87 m_notifierToIdMap.set(WTFMove(notifier), id);
91 GeoNotifier* Geolocation::Watchers::find(int id)
94 return m_idToNotifierMap.get(id);
97 void Geolocation::Watchers::remove(int id)
100 if (auto notifier = m_idToNotifierMap.take(id))
101 m_notifierToIdMap.remove(notifier);
104 void Geolocation::Watchers::remove(GeoNotifier* notifier)
106 if (auto identifier = m_notifierToIdMap.take(notifier))
107 m_idToNotifierMap.remove(identifier);
110 bool Geolocation::Watchers::contains(GeoNotifier* notifier) const
112 return m_notifierToIdMap.contains(notifier);
115 void Geolocation::Watchers::clear()
117 m_idToNotifierMap.clear();
118 m_notifierToIdMap.clear();
121 bool Geolocation::Watchers::isEmpty() const
123 return m_idToNotifierMap.isEmpty();
126 void Geolocation::Watchers::getNotifiersVector(GeoNotifierVector& copy) const
128 copyValuesToVector(m_idToNotifierMap, copy);
131 Ref<Geolocation> Geolocation::create(ScriptExecutionContext* context)
133 auto geolocation = adoptRef(*new Geolocation(context));
134 geolocation.get().suspendIfNeeded();
138 Geolocation::Geolocation(ScriptExecutionContext* context)
139 : ActiveDOMObject(context)
140 , m_allowGeolocation(Unknown)
141 , m_isSuspended(false)
142 , m_hasChangedPosition(false)
143 , m_resumeTimer(*this, &Geolocation::resumeTimerFired)
147 Geolocation::~Geolocation()
149 ASSERT(m_allowGeolocation != InProgress);
152 Document* Geolocation::document() const
154 return downcast<Document>(scriptExecutionContext());
157 SecurityOrigin* Geolocation::securityOrigin() const
159 return scriptExecutionContext()->securityOrigin();
162 Frame* Geolocation::frame() const
164 return document() ? document()->frame() : nullptr;
167 Page* Geolocation::page() const
169 return document() ? document()->page() : nullptr;
172 bool Geolocation::canSuspendForDocumentSuspension() const
177 void Geolocation::suspend(ReasonForSuspension reason)
179 if (reason == ActiveDOMObject::PageCache) {
181 m_resetOnResume = true;
184 // Suspend GeoNotifier timeout timers.
188 m_isSuspended = true;
189 m_resumeTimer.stop();
190 ActiveDOMObject::suspend(reason);
193 void Geolocation::resume()
196 ASSERT(WebThreadIsLockedOrDisabled());
198 ActiveDOMObject::resume();
200 if (!m_resumeTimer.isActive())
201 m_resumeTimer.startOneShot(0);
204 void Geolocation::resumeTimerFired()
206 m_isSuspended = false;
208 if (m_resetOnResume) {
209 resetAllGeolocationPermission();
210 m_resetOnResume = false;
213 // Resume GeoNotifier timeout timers.
214 if (hasListeners()) {
215 for (auto& notifier : m_oneShots)
216 notifier->startTimerIfNeeded();
217 GeoNotifierVector watcherCopy;
218 m_watchers.getNotifiersVector(watcherCopy);
219 for (auto& watcher : watcherCopy)
220 watcher->startTimerIfNeeded();
223 if ((isAllowed() || isDenied()) && !m_pendingForPermissionNotifiers.isEmpty()) {
224 // The pending permission was granted while the object was suspended.
225 setIsAllowed(isAllowed());
226 ASSERT(!m_hasChangedPosition);
227 ASSERT(!m_errorWaitingForResume);
231 if (isDenied() && hasListeners()) {
232 // The permission was revoked while the object was suspended.
237 if (m_hasChangedPosition) {
239 m_hasChangedPosition = false;
242 if (m_errorWaitingForResume) {
243 handleError(m_errorWaitingForResume.get());
244 m_errorWaitingForResume = nullptr;
248 void Geolocation::resetAllGeolocationPermission()
251 m_resetOnResume = true;
255 if (m_allowGeolocation == InProgress) {
256 Page* page = this->page();
258 GeolocationController::from(page)->cancelPermissionRequest(this);
260 // This return is not technically correct as GeolocationController::cancelPermissionRequest() should have cleared the active request.
261 // Neither iOS nor OS X supports cancelPermissionRequest() (https://bugs.webkit.org/show_bug.cgi?id=89524), so we workaround that and let ongoing requests complete. :(
265 // 1) Reset our own state.
267 m_allowGeolocation = Unknown;
268 m_hasChangedPosition = false;
269 m_errorWaitingForResume = nullptr;
271 // 2) Request new permission for the active notifiers.
274 // Go over the one shot and re-request permission.
275 for (auto& notifier : m_oneShots)
276 startRequest(notifier.get());
277 // Go over the watchers and re-request permission.
278 GeoNotifierVector watcherCopy;
279 m_watchers.getNotifiersVector(watcherCopy);
280 for (auto& watcher : watcherCopy)
281 startRequest(watcher.get());
284 void Geolocation::stop()
286 Page* page = this->page();
287 if (page && m_allowGeolocation == InProgress)
288 GeolocationController::from(page)->cancelPermissionRequest(this);
289 // The frame may be moving to a new page and we want to get the permissions from the new page's client.
290 m_allowGeolocation = Unknown;
293 m_hasChangedPosition = false;
294 m_errorWaitingForResume = nullptr;
295 m_pendingForPermissionNotifiers.clear();
298 const char* Geolocation::activeDOMObjectName() const
300 return "Geolocation";
303 Geoposition* Geolocation::lastPosition()
305 Page* page = this->page();
309 m_lastPosition = createGeoposition(GeolocationController::from(page)->lastPosition());
311 return m_lastPosition.get();
314 void Geolocation::getCurrentPosition(RefPtr<PositionCallback>&& successCallback, RefPtr<PositionErrorCallback>&& errorCallback, RefPtr<PositionOptions>&& options)
319 RefPtr<GeoNotifier> notifier = GeoNotifier::create(*this, WTFMove(successCallback), WTFMove(errorCallback), WTFMove(options));
320 startRequest(notifier.get());
322 m_oneShots.add(notifier);
325 int Geolocation::watchPosition(RefPtr<PositionCallback>&& successCallback, RefPtr<PositionErrorCallback>&& errorCallback, RefPtr<PositionOptions>&& options)
330 RefPtr<GeoNotifier> notifier = GeoNotifier::create(*this, WTFMove(successCallback), WTFMove(errorCallback), WTFMove(options));
331 startRequest(notifier.get());
334 // Keep asking for the next id until we're given one that we don't already have.
336 watchID = m_scriptExecutionContext->circularSequentialID();
337 } while (!m_watchers.add(watchID, WTFMove(notifier)));
341 static void logError(const String& target, const bool isSecure, const bool isMixedContent, Document* document)
343 StringBuilder message;
344 message.append("[blocked] Access to geolocation was blocked over");
347 message.append(" insecure connection to ");
348 else if (isMixedContent)
349 message.append(" secure connection with mixed content to ");
353 message.append(target);
354 message.append(".\n");
355 document->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message.toString());
358 bool Geolocation::shouldBlockGeolocationRequests()
360 bool isSecure = SecurityOrigin::isSecure(document()->url());
361 bool hasMixedContent = document()->foundMixedContent();
362 bool isLocalFile = document()->url().isLocalFile();
363 if (securityOrigin()->canRequestGeolocation()) {
364 if (isLocalFile || (isSecure && !hasMixedContent))
368 logError(securityOrigin()->toString(), isSecure, hasMixedContent, document());
372 void Geolocation::startRequest(GeoNotifier* notifier)
374 if (shouldBlockGeolocationRequests()) {
375 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, ASCIILiteral(originCannotRequestGeolocationErrorMessage)));
378 document()->setGeolocationAccessed();
380 // Check whether permissions have already been denied. Note that if this is the case,
381 // the permission state can not change again in the lifetime of this page.
383 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, ASCIILiteral(permissionDeniedErrorMessage)));
384 else if (haveSuitableCachedPosition(notifier->options()))
385 notifier->setUseCachedPosition();
386 else if (notifier->hasZeroTimeout())
387 notifier->startTimerIfNeeded();
388 else if (!isAllowed()) {
389 // if we don't yet have permission, request for permission before calling startUpdating()
390 m_pendingForPermissionNotifiers.add(notifier);
392 } else if (startUpdating(notifier))
393 notifier->startTimerIfNeeded();
395 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, ASCIILiteral(failedToStartServiceErrorMessage)));
398 void Geolocation::fatalErrorOccurred(GeoNotifier* notifier)
400 // This request has failed fatally. Remove it from our lists.
401 m_oneShots.remove(notifier);
402 m_watchers.remove(notifier);
408 void Geolocation::requestUsesCachedPosition(GeoNotifier* notifier)
410 // This is called asynchronously, so the permissions could have been denied
411 // since we last checked in startRequest.
413 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, ASCIILiteral(permissionDeniedErrorMessage)));
417 m_requestsAwaitingCachedPosition.add(notifier);
419 // If permissions are allowed, make the callback
421 makeCachedPositionCallbacks();
425 // Request permissions, which may be synchronous or asynchronous.
429 void Geolocation::makeCachedPositionCallbacks()
431 // All modifications to m_requestsAwaitingCachedPosition are done
432 // asynchronously, so we don't need to worry about it being modified from
434 for (auto& notifier : m_requestsAwaitingCachedPosition) {
435 notifier->runSuccessCallback(lastPosition());
437 // If this is a one-shot request, stop it. Otherwise, if the watch still
438 // exists, start the service to get updates.
439 if (!m_oneShots.remove(notifier.get()) && m_watchers.contains(notifier.get())) {
440 if (notifier->hasZeroTimeout() || startUpdating(notifier.get()))
441 notifier->startTimerIfNeeded();
443 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, ASCIILiteral(failedToStartServiceErrorMessage)));
447 m_requestsAwaitingCachedPosition.clear();
453 void Geolocation::requestTimedOut(GeoNotifier* notifier)
455 // If this is a one-shot request, stop it.
456 m_oneShots.remove(notifier);
462 bool Geolocation::haveSuitableCachedPosition(PositionOptions* options)
464 Geoposition* cachedPosition = lastPosition();
467 if (!options->hasMaximumAge())
469 if (!options->maximumAge())
471 DOMTimeStamp currentTimeMillis = convertSecondsToDOMTimeStamp(currentTime());
472 return cachedPosition->timestamp() > currentTimeMillis - options->maximumAge();
475 void Geolocation::clearWatch(int watchID)
480 if (GeoNotifier* notifier = m_watchers.find(watchID))
481 m_pendingForPermissionNotifiers.remove(notifier);
482 m_watchers.remove(watchID);
488 void Geolocation::setIsAllowed(bool allowed)
490 // Protect the Geolocation object from garbage collection during a callback.
491 Ref<Geolocation> protectedThis(*this);
493 // This may be due to either a new position from the service, or a cached
495 m_allowGeolocation = allowed ? Yes : No;
500 // Permission request was made during the startRequest process
501 if (!m_pendingForPermissionNotifiers.isEmpty()) {
502 handlePendingPermissionNotifiers();
503 m_pendingForPermissionNotifiers.clear();
508 RefPtr<PositionError> error = PositionError::create(PositionError::PERMISSION_DENIED, ASCIILiteral(permissionDeniedErrorMessage));
509 error->setIsFatal(true);
510 handleError(error.get());
511 m_requestsAwaitingCachedPosition.clear();
512 m_hasChangedPosition = false;
513 m_errorWaitingForResume = nullptr;
518 // If the service has a last position, use it to call back for all requests.
519 // If any of the requests are waiting for permission for a cached position,
520 // the position from the service will be at least as fresh.
522 makeSuccessCallbacks();
524 makeCachedPositionCallbacks();
527 void Geolocation::sendError(GeoNotifierVector& notifiers, PositionError* error)
529 for (auto& notifier : notifiers)
530 notifier->runErrorCallback(error);
533 void Geolocation::sendPosition(GeoNotifierVector& notifiers, Geoposition* position)
535 for (auto& notifier : notifiers)
536 notifier->runSuccessCallback(position);
539 void Geolocation::stopTimer(GeoNotifierVector& notifiers)
541 for (auto& notifier : notifiers)
542 notifier->stopTimer();
545 void Geolocation::stopTimersForOneShots()
547 GeoNotifierVector copy;
548 copyToVector(m_oneShots, copy);
553 void Geolocation::stopTimersForWatchers()
555 GeoNotifierVector copy;
556 m_watchers.getNotifiersVector(copy);
561 void Geolocation::stopTimers()
563 stopTimersForOneShots();
564 stopTimersForWatchers();
567 void Geolocation::cancelRequests(GeoNotifierVector& notifiers)
569 for (auto& notifier : notifiers)
570 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, ASCIILiteral(framelessDocumentErrorMessage)));
573 void Geolocation::cancelAllRequests()
575 GeoNotifierVector copy;
576 copyToVector(m_oneShots, copy);
577 cancelRequests(copy);
578 m_watchers.getNotifiersVector(copy);
579 cancelRequests(copy);
582 void Geolocation::extractNotifiersWithCachedPosition(GeoNotifierVector& notifiers, GeoNotifierVector* cached)
584 GeoNotifierVector nonCached;
585 for (auto& notifier : notifiers) {
586 if (notifier->useCachedPosition()) {
588 cached->append(notifier.get());
590 nonCached.append(notifier.get());
592 notifiers.swap(nonCached);
595 void Geolocation::copyToSet(const GeoNotifierVector& src, GeoNotifierSet& dest)
597 for (auto& notifier : src)
598 dest.add(notifier.get());
601 void Geolocation::handleError(PositionError* error)
605 GeoNotifierVector oneShotsCopy;
606 copyToVector(m_oneShots, oneShotsCopy);
608 GeoNotifierVector watchersCopy;
609 m_watchers.getNotifiersVector(watchersCopy);
611 // Clear the lists before we make the callbacks, to avoid clearing notifiers
612 // added by calls to Geolocation methods from the callbacks, and to prevent
613 // further callbacks to these notifiers.
614 GeoNotifierVector oneShotsWithCachedPosition;
616 if (error->isFatal())
619 // Don't send non-fatal errors to notifiers due to receive a cached position.
620 extractNotifiersWithCachedPosition(oneShotsCopy, &oneShotsWithCachedPosition);
621 extractNotifiersWithCachedPosition(watchersCopy, 0);
624 sendError(oneShotsCopy, error);
625 sendError(watchersCopy, error);
627 // hasListeners() doesn't distinguish between notifiers due to receive a
628 // cached position and those requiring a fresh position. Perform the check
629 // before restoring the notifiers below.
633 // Maintain a reference to the cached notifiers until their timer fires.
634 copyToSet(oneShotsWithCachedPosition, m_oneShots);
637 void Geolocation::requestPermission()
639 if (m_allowGeolocation > Unknown)
642 Page* page = this->page();
646 m_allowGeolocation = InProgress;
648 // Ask the embedder: it maintains the geolocation challenge policy itself.
649 GeolocationController::from(page)->requestPermission(this);
652 void Geolocation::makeSuccessCallbacks()
654 ASSERT(lastPosition());
657 GeoNotifierVector oneShotsCopy;
658 copyToVector(m_oneShots, oneShotsCopy);
660 GeoNotifierVector watchersCopy;
661 m_watchers.getNotifiersVector(watchersCopy);
663 // Clear the lists before we make the callbacks, to avoid clearing notifiers
664 // added by calls to Geolocation methods from the callbacks, and to prevent
665 // further callbacks to these notifiers.
668 sendPosition(oneShotsCopy, lastPosition());
669 sendPosition(watchersCopy, lastPosition());
675 void Geolocation::positionChanged()
679 // Stop all currently running timers.
683 m_hasChangedPosition = true;
687 makeSuccessCallbacks();
690 void Geolocation::setError(GeolocationError* error)
693 m_errorWaitingForResume = createPositionError(error);
696 RefPtr<PositionError> positionError = createPositionError(error);
697 handleError(positionError.get());
700 bool Geolocation::startUpdating(GeoNotifier* notifier)
702 Page* page = this->page();
706 GeolocationController::from(page)->addObserver(this, notifier->options()->enableHighAccuracy());
710 void Geolocation::stopUpdating()
712 Page* page = this->page();
716 GeolocationController::from(page)->removeObserver(this);
719 void Geolocation::handlePendingPermissionNotifiers()
721 // While we iterate through the list, we need not worry about list being modified as the permission
722 // is already set to Yes/No and no new listeners will be added to the pending list
723 for (auto& notifier : m_pendingForPermissionNotifiers) {
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.get()))
728 notifier->startTimerIfNeeded();
730 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, ASCIILiteral(failedToStartServiceErrorMessage)));
732 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, ASCIILiteral(permissionDeniedErrorMessage)));
736 } // namespace WebCore
738 #endif // ENABLE(GEOLOCATION)