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