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 38namespace blink { 39 40static const char permissionDeniedErrorMessage[] = "User denied Geolocation"; 41static const char failedToStartServiceErrorMessage[] = "Failed to start Geolocation service"; 42static const char framelessDocumentErrorMessage[] = "Geolocation cannot be used in frameless documents"; 43 44static 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 64static 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 79Geolocation* Geolocation::create(ExecutionContext* context) 80{ 81 Geolocation* geolocation = new Geolocation(context); 82 geolocation->suspendIfNeeded(); 83 return geolocation; 84} 85 86Geolocation::Geolocation(ExecutionContext* context) 87 : ActiveDOMObject(context) 88 , m_geolocationPermission(PermissionUnknown) 89{ 90} 91 92Geolocation::~Geolocation() 93{ 94 ASSERT(m_geolocationPermission != PermissionRequested); 95} 96 97void 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 106Document* Geolocation::document() const 107{ 108 return toDocument(executionContext()); 109} 110 111LocalFrame* Geolocation::frame() const 112{ 113 return document() ? document()->frame() : 0; 114} 115 116void 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 129Geoposition* 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 140void 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 151int 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 167void 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 187void 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 197void 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 218void 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 246void 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 255bool 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 266void 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 279void 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 308void 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 315void 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 322void 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 329void Geolocation::stopTimersForOneShots() 330{ 331 GeoNotifierVector copy; 332 copyToVector(m_oneShots, copy); 333 334 stopTimer(copy); 335} 336 337void Geolocation::stopTimersForWatchers() 338{ 339 GeoNotifierVector copy; 340 m_watchers.getNotifiersVector(copy); 341 342 stopTimer(copy); 343} 344 345void Geolocation::stopTimers() 346{ 347 stopTimersForOneShots(); 348 stopTimersForWatchers(); 349} 350 351void 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 358void Geolocation::cancelAllRequests() 359{ 360 GeoNotifierVector copy; 361 copyToVector(m_oneShots, copy); 362 cancelRequests(copy); 363 m_watchers.getNotifiersVector(copy); 364 cancelRequests(copy); 365} 366 367void 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 382void 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 391void 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 427void 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 442void 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 470void Geolocation::positionChanged() 471{ 472 ASSERT(isAllowed()); 473 474 // Stop all currently running timers. 475 stopTimers(); 476 477 makeSuccessCallbacks(); 478} 479 480void Geolocation::setError(GeolocationError* error) 481{ 482 handleError(createPositionError(error)); 483} 484 485bool 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 495void Geolocation::stopUpdating() 496{ 497 LocalFrame* frame = this->frame(); 498 if (!frame) 499 return; 500 501 GeolocationController::from(frame)->removeObserver(this); 502} 503 504void 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