Home | History | Annotate | Download | only in geolocation
      1 /*
      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
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions
      8  * are met:
      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.
     14  *
     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.
     26  */
     27 
     28 #include "config.h"
     29 #include "modules/geolocation/Geolocation.h"
     30 
     31 #include "core/dom/Document.h"
     32 #include "modules/geolocation/Coordinates.h"
     33 #include "modules/geolocation/GeolocationController.h"
     34 #include "modules/geolocation/GeolocationError.h"
     35 #include "modules/geolocation/GeolocationPosition.h"
     36 #include "wtf/CurrentTime.h"
     37 
     38 namespace blink {
     39 
     40 static const char permissionDeniedErrorMessage[] = "User denied Geolocation";
     41 static const char failedToStartServiceErrorMessage[] = "Failed to start Geolocation service";
     42 static const char framelessDocumentErrorMessage[] = "Geolocation cannot be used in frameless documents";
     43 
     44 static Geoposition* createGeoposition(GeolocationPosition* position)
     45 {
     46     if (!position)
     47         return nullptr;
     48 
     49     Coordinates* coordinates = Coordinates::create(
     50         position->latitude(),
     51         position->longitude(),
     52         position->canProvideAltitude(),
     53         position->altitude(),
     54         position->accuracy(),
     55         position->canProvideAltitudeAccuracy(),
     56         position->altitudeAccuracy(),
     57         position->canProvideHeading(),
     58         position->heading(),
     59         position->canProvideSpeed(),
     60         position->speed());
     61     return Geoposition::create(coordinates, convertSecondsToDOMTimeStamp(position->timestamp()));
     62 }
     63 
     64 static PositionError* createPositionError(GeolocationError* error)
     65 {
     66     PositionError::ErrorCode code = PositionError::POSITION_UNAVAILABLE;
     67     switch (error->code()) {
     68     case GeolocationError::PermissionDenied:
     69         code = PositionError::PERMISSION_DENIED;
     70         break;
     71     case GeolocationError::PositionUnavailable:
     72         code = PositionError::POSITION_UNAVAILABLE;
     73         break;
     74     }
     75 
     76     return PositionError::create(code, error->message());
     77 }
     78 
     79 Geolocation* Geolocation::create(ExecutionContext* context)
     80 {
     81     Geolocation* geolocation = new Geolocation(context);
     82     geolocation->suspendIfNeeded();
     83     return geolocation;
     84 }
     85 
     86 Geolocation::Geolocation(ExecutionContext* context)
     87     : ActiveDOMObject(context)
     88     , m_geolocationPermission(PermissionUnknown)
     89 {
     90 }
     91 
     92 Geolocation::~Geolocation()
     93 {
     94     ASSERT(m_geolocationPermission != PermissionRequested);
     95 }
     96 
     97 void Geolocation::trace(Visitor* visitor)
     98 {
     99     visitor->trace(m_oneShots);
    100     visitor->trace(m_watchers);
    101     visitor->trace(m_pendingForPermissionNotifiers);
    102     visitor->trace(m_lastPosition);
    103     visitor->trace(m_requestsAwaitingCachedPosition);
    104 }
    105 
    106 Document* Geolocation::document() const
    107 {
    108     return toDocument(executionContext());
    109 }
    110 
    111 LocalFrame* Geolocation::frame() const
    112 {
    113     return document() ? document()->frame() : 0;
    114 }
    115 
    116 void Geolocation::stop()
    117 {
    118     LocalFrame* frame = this->frame();
    119     if (frame && m_geolocationPermission == PermissionRequested)
    120         GeolocationController::from(frame)->cancelPermissionRequest(this);
    121 
    122     // The frame may be moving to a new page and we want to get the permissions from the new page's client.
    123     m_geolocationPermission = PermissionUnknown;
    124     cancelAllRequests();
    125     stopUpdating();
    126     m_pendingForPermissionNotifiers.clear();
    127 }
    128 
    129 Geoposition* Geolocation::lastPosition()
    130 {
    131     LocalFrame* frame = this->frame();
    132     if (!frame)
    133         return 0;
    134 
    135     m_lastPosition = createGeoposition(GeolocationController::from(frame)->lastPosition());
    136 
    137     return m_lastPosition.get();
    138 }
    139 
    140 void Geolocation::getCurrentPosition(PositionCallback* successCallback, PositionErrorCallback* errorCallback, const Dictionary& options)
    141 {
    142     if (!frame())
    143         return;
    144 
    145     GeoNotifier* notifier = GeoNotifier::create(this, successCallback, errorCallback, PositionOptions::create(options));
    146     startRequest(notifier);
    147 
    148     m_oneShots.add(notifier);
    149 }
    150 
    151 int Geolocation::watchPosition(PositionCallback* successCallback, PositionErrorCallback* errorCallback, const Dictionary& options)
    152 {
    153     if (!frame())
    154         return 0;
    155 
    156     GeoNotifier* notifier = GeoNotifier::create(this, successCallback, errorCallback, PositionOptions::create(options));
    157     startRequest(notifier);
    158 
    159     int watchID;
    160     // Keep asking for the next id until we're given one that we don't already have.
    161     do {
    162         watchID = executionContext()->circularSequentialID();
    163     } while (!m_watchers.add(watchID, notifier));
    164     return watchID;
    165 }
    166 
    167 void Geolocation::startRequest(GeoNotifier *notifier)
    168 {
    169     // Check whether permissions have already been denied. Note that if this is the case,
    170     // the permission state can not change again in the lifetime of this page.
    171     if (isDenied())
    172         notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
    173     else if (haveSuitableCachedPosition(notifier->options()))
    174         notifier->setUseCachedPosition();
    175     else if (!notifier->options()->timeout())
    176         notifier->startTimer();
    177     else if (!isAllowed()) {
    178         // if we don't yet have permission, request for permission before calling startUpdating()
    179         m_pendingForPermissionNotifiers.add(notifier);
    180         requestPermission();
    181     } else if (startUpdating(notifier))
    182         notifier->startTimer();
    183     else
    184         notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
    185 }
    186 
    187 void Geolocation::fatalErrorOccurred(GeoNotifier* notifier)
    188 {
    189     // This request has failed fatally. Remove it from our lists.
    190     m_oneShots.remove(notifier);
    191     m_watchers.remove(notifier);
    192 
    193     if (!hasListeners())
    194         stopUpdating();
    195 }
    196 
    197 void Geolocation::requestUsesCachedPosition(GeoNotifier* notifier)
    198 {
    199     // This is called asynchronously, so the permissions could have been denied
    200     // since we last checked in startRequest.
    201     if (isDenied()) {
    202         notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
    203         return;
    204     }
    205 
    206     m_requestsAwaitingCachedPosition.add(notifier);
    207 
    208     // If permissions are allowed, make the callback
    209     if (isAllowed()) {
    210         makeCachedPositionCallbacks();
    211         return;
    212     }
    213 
    214     // Request permissions, which may be synchronous or asynchronous.
    215     requestPermission();
    216 }
    217 
    218 void Geolocation::makeCachedPositionCallbacks()
    219 {
    220     // All modifications to m_requestsAwaitingCachedPosition are done
    221     // asynchronously, so we don't need to worry about it being modified from
    222     // the callbacks.
    223     GeoNotifierSet::const_iterator end = m_requestsAwaitingCachedPosition.end();
    224     for (GeoNotifierSet::const_iterator iter = m_requestsAwaitingCachedPosition.begin(); iter != end; ++iter) {
    225         GeoNotifier* notifier = iter->get();
    226         notifier->runSuccessCallback(lastPosition());
    227 
    228         // If this is a one-shot request, stop it. Otherwise, if the watch still
    229         // exists, start the service to get updates.
    230         if (m_oneShots.contains(notifier))
    231             m_oneShots.remove(notifier);
    232         else if (m_watchers.contains(notifier)) {
    233             if (!notifier->options()->timeout() || startUpdating(notifier))
    234                 notifier->startTimer();
    235             else
    236                 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
    237         }
    238     }
    239 
    240     m_requestsAwaitingCachedPosition.clear();
    241 
    242     if (!hasListeners())
    243         stopUpdating();
    244 }
    245 
    246 void Geolocation::requestTimedOut(GeoNotifier* notifier)
    247 {
    248     // If this is a one-shot request, stop it.
    249     m_oneShots.remove(notifier);
    250 
    251     if (!hasListeners())
    252         stopUpdating();
    253 }
    254 
    255 bool Geolocation::haveSuitableCachedPosition(PositionOptions* options)
    256 {
    257     Geoposition* cachedPosition = lastPosition();
    258     if (!cachedPosition)
    259         return false;
    260     if (!options->maximumAge())
    261         return false;
    262     DOMTimeStamp currentTimeMillis = convertSecondsToDOMTimeStamp(currentTime());
    263     return cachedPosition->timestamp() > currentTimeMillis - options->maximumAge();
    264 }
    265 
    266 void Geolocation::clearWatch(int watchID)
    267 {
    268     if (watchID <= 0)
    269         return;
    270 
    271     if (GeoNotifier* notifier = m_watchers.find(watchID))
    272         m_pendingForPermissionNotifiers.remove(notifier);
    273     m_watchers.remove(watchID);
    274 
    275     if (!hasListeners())
    276         stopUpdating();
    277 }
    278 
    279 void Geolocation::setIsAllowed(bool allowed)
    280 {
    281     // This may be due to either a new position from the service, or a cached position.
    282     m_geolocationPermission = allowed ? PermissionAllowed : PermissionDenied;
    283 
    284     // Permission request was made during the startRequest process
    285     if (!m_pendingForPermissionNotifiers.isEmpty()) {
    286         handlePendingPermissionNotifiers();
    287         m_pendingForPermissionNotifiers.clear();
    288         return;
    289     }
    290 
    291     if (!isAllowed()) {
    292         PositionError* error = PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage);
    293         error->setIsFatal(true);
    294         handleError(error);
    295         m_requestsAwaitingCachedPosition.clear();
    296         return;
    297     }
    298 
    299     // If the service has a last position, use it to call back for all requests.
    300     // If any of the requests are waiting for permission for a cached position,
    301     // the position from the service will be at least as fresh.
    302     if (lastPosition())
    303         makeSuccessCallbacks();
    304     else
    305         makeCachedPositionCallbacks();
    306 }
    307 
    308 void Geolocation::sendError(GeoNotifierVector& notifiers, PositionError* error)
    309 {
    310     GeoNotifierVector::const_iterator end = notifiers.end();
    311     for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
    312         (*it)->runErrorCallback(error);
    313 }
    314 
    315 void Geolocation::sendPosition(GeoNotifierVector& notifiers, Geoposition* position)
    316 {
    317     GeoNotifierVector::const_iterator end = notifiers.end();
    318     for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
    319         (*it)->runSuccessCallback(position);
    320 }
    321 
    322 void Geolocation::stopTimer(GeoNotifierVector& notifiers)
    323 {
    324     GeoNotifierVector::const_iterator end = notifiers.end();
    325     for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
    326         (*it)->stopTimer();
    327 }
    328 
    329 void Geolocation::stopTimersForOneShots()
    330 {
    331     GeoNotifierVector copy;
    332     copyToVector(m_oneShots, copy);
    333 
    334     stopTimer(copy);
    335 }
    336 
    337 void Geolocation::stopTimersForWatchers()
    338 {
    339     GeoNotifierVector copy;
    340     m_watchers.getNotifiersVector(copy);
    341 
    342     stopTimer(copy);
    343 }
    344 
    345 void Geolocation::stopTimers()
    346 {
    347     stopTimersForOneShots();
    348     stopTimersForWatchers();
    349 }
    350 
    351 void Geolocation::cancelRequests(GeoNotifierVector& notifiers)
    352 {
    353     GeoNotifierVector::const_iterator end = notifiers.end();
    354     for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
    355         (*it)->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, framelessDocumentErrorMessage));
    356 }
    357 
    358 void Geolocation::cancelAllRequests()
    359 {
    360     GeoNotifierVector copy;
    361     copyToVector(m_oneShots, copy);
    362     cancelRequests(copy);
    363     m_watchers.getNotifiersVector(copy);
    364     cancelRequests(copy);
    365 }
    366 
    367 void Geolocation::extractNotifiersWithCachedPosition(GeoNotifierVector& notifiers, GeoNotifierVector* cached)
    368 {
    369     GeoNotifierVector nonCached;
    370     GeoNotifierVector::iterator end = notifiers.end();
    371     for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) {
    372         GeoNotifier* notifier = it->get();
    373         if (notifier->useCachedPosition()) {
    374             if (cached)
    375                 cached->append(notifier);
    376         } else
    377             nonCached.append(notifier);
    378     }
    379     notifiers.swap(nonCached);
    380 }
    381 
    382 void Geolocation::copyToSet(const GeoNotifierVector& src, GeoNotifierSet& dest)
    383 {
    384      GeoNotifierVector::const_iterator end = src.end();
    385      for (GeoNotifierVector::const_iterator it = src.begin(); it != end; ++it) {
    386          GeoNotifier* notifier = it->get();
    387          dest.add(notifier);
    388      }
    389 }
    390 
    391 void Geolocation::handleError(PositionError* error)
    392 {
    393     ASSERT(error);
    394 
    395     GeoNotifierVector oneShotsCopy;
    396     copyToVector(m_oneShots, oneShotsCopy);
    397 
    398     GeoNotifierVector watchersCopy;
    399     m_watchers.getNotifiersVector(watchersCopy);
    400 
    401     // Clear the lists before we make the callbacks, to avoid clearing notifiers
    402     // added by calls to Geolocation methods from the callbacks, and to prevent
    403     // further callbacks to these notifiers.
    404     GeoNotifierVector oneShotsWithCachedPosition;
    405     m_oneShots.clear();
    406     if (error->isFatal())
    407         m_watchers.clear();
    408     else {
    409         // Don't send non-fatal errors to notifiers due to receive a cached position.
    410         extractNotifiersWithCachedPosition(oneShotsCopy, &oneShotsWithCachedPosition);
    411         extractNotifiersWithCachedPosition(watchersCopy, 0);
    412     }
    413 
    414     sendError(oneShotsCopy, error);
    415     sendError(watchersCopy, error);
    416 
    417     // hasListeners() doesn't distinguish between notifiers due to receive a
    418     // cached position and those requiring a fresh position. Perform the check
    419     // before restoring the notifiers below.
    420     if (!hasListeners())
    421         stopUpdating();
    422 
    423     // Maintain a reference to the cached notifiers until their timer fires.
    424     copyToSet(oneShotsWithCachedPosition, m_oneShots);
    425 }
    426 
    427 void Geolocation::requestPermission()
    428 {
    429     if (m_geolocationPermission != PermissionUnknown)
    430         return;
    431 
    432     LocalFrame* frame = this->frame();
    433     if (!frame)
    434         return;
    435 
    436     m_geolocationPermission = PermissionRequested;
    437 
    438     // Ask the embedder: it maintains the geolocation challenge policy itself.
    439     GeolocationController::from(frame)->requestPermission(this);
    440 }
    441 
    442 void Geolocation::makeSuccessCallbacks()
    443 {
    444     ASSERT(lastPosition());
    445     ASSERT(isAllowed());
    446 
    447     GeoNotifierVector oneShotsCopy;
    448     copyToVector(m_oneShots, oneShotsCopy);
    449 
    450     GeoNotifierVector watchersCopy;
    451     m_watchers.getNotifiersVector(watchersCopy);
    452 
    453     // Clear the lists before we make the callbacks, to avoid clearing notifiers
    454     // added by calls to Geolocation methods from the callbacks, and to prevent
    455     // further callbacks to these notifiers.
    456     m_oneShots.clear();
    457 
    458     // Also clear the set of notifiers waiting for a cached position. All the
    459     // oneshots and watchers will receive a position now, and if they happen to
    460     // be lingering in that set, avoid this bug: http://crbug.com/311876 .
    461     m_requestsAwaitingCachedPosition.clear();
    462 
    463     sendPosition(oneShotsCopy, lastPosition());
    464     sendPosition(watchersCopy, lastPosition());
    465 
    466     if (!hasListeners())
    467         stopUpdating();
    468 }
    469 
    470 void Geolocation::positionChanged()
    471 {
    472     ASSERT(isAllowed());
    473 
    474     // Stop all currently running timers.
    475     stopTimers();
    476 
    477     makeSuccessCallbacks();
    478 }
    479 
    480 void Geolocation::setError(GeolocationError* error)
    481 {
    482     handleError(createPositionError(error));
    483 }
    484 
    485 bool Geolocation::startUpdating(GeoNotifier* notifier)
    486 {
    487     LocalFrame* frame = this->frame();
    488     if (!frame)
    489         return false;
    490 
    491     GeolocationController::from(frame)->addObserver(this, notifier->options()->enableHighAccuracy());
    492     return true;
    493 }
    494 
    495 void Geolocation::stopUpdating()
    496 {
    497     LocalFrame* frame = this->frame();
    498     if (!frame)
    499         return;
    500 
    501     GeolocationController::from(frame)->removeObserver(this);
    502 }
    503 
    504 void Geolocation::handlePendingPermissionNotifiers()
    505 {
    506     // While we iterate through the list, we need not worry about list being modified as the permission
    507     // is already set to Yes/No and no new listeners will be added to the pending list
    508     GeoNotifierSet::const_iterator end = m_pendingForPermissionNotifiers.end();
    509     for (GeoNotifierSet::const_iterator iter = m_pendingForPermissionNotifiers.begin(); iter != end; ++iter) {
    510         GeoNotifier* notifier = iter->get();
    511 
    512         if (isAllowed()) {
    513             // start all pending notification requests as permission granted.
    514             // The notifier is always ref'ed by m_oneShots or m_watchers.
    515             if (startUpdating(notifier))
    516                 notifier->startTimer();
    517             else
    518                 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
    519         } else {
    520             notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
    521         }
    522     }
    523 }
    524 
    525 } // namespace blink
    526