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 "Geolocation.h" 30 31#if ENABLE(GEOLOCATION) 32 33#include "Document.h" 34#include "Frame.h" 35#include "Geoposition.h" 36#include "Page.h" 37#include <wtf/CurrentTime.h> 38#include <wtf/Ref.h> 39 40#include "Coordinates.h" 41#include "GeolocationController.h" 42#include "GeolocationError.h" 43#include "GeolocationPosition.h" 44#include "PositionError.h" 45 46namespace WebCore { 47 48static const char permissionDeniedErrorMessage[] = "User denied Geolocation"; 49static const char failedToStartServiceErrorMessage[] = "Failed to start Geolocation service"; 50static const char framelessDocumentErrorMessage[] = "Geolocation cannot be used in frameless documents"; 51 52static PassRefPtr<Geoposition> createGeoposition(GeolocationPosition* position) 53{ 54 if (!position) 55 return 0; 56 57 RefPtr<Coordinates> coordinates = Coordinates::create(position->latitude(), position->longitude(), position->canProvideAltitude(), position->altitude(), 58 position->accuracy(), position->canProvideAltitudeAccuracy(), position->altitudeAccuracy(), 59 position->canProvideHeading(), position->heading(), position->canProvideSpeed(), position->speed()); 60 return Geoposition::create(coordinates.release(), convertSecondsToDOMTimeStamp(position->timestamp())); 61} 62 63static PassRefPtr<PositionError> createPositionError(GeolocationError* error) 64{ 65 PositionError::ErrorCode code = PositionError::POSITION_UNAVAILABLE; 66 switch (error->code()) { 67 case GeolocationError::PermissionDenied: 68 code = PositionError::PERMISSION_DENIED; 69 break; 70 case GeolocationError::PositionUnavailable: 71 code = PositionError::POSITION_UNAVAILABLE; 72 break; 73 } 74 75 return PositionError::create(code, error->message()); 76} 77 78Geolocation::GeoNotifier::GeoNotifier(Geolocation* geolocation, PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options) 79 : m_geolocation(geolocation) 80 , m_successCallback(successCallback) 81 , m_errorCallback(errorCallback) 82 , m_options(options) 83 , m_timer(this, &Geolocation::GeoNotifier::timerFired) 84 , m_useCachedPosition(false) 85{ 86 ASSERT(m_geolocation); 87 ASSERT(m_successCallback); 88 // If no options were supplied from JS, we should have created a default set 89 // of options in JSGeolocationCustom.cpp. 90 ASSERT(m_options); 91} 92 93void Geolocation::GeoNotifier::setFatalError(PassRefPtr<PositionError> error) 94{ 95 // If a fatal error has already been set, stick with it. This makes sure that 96 // when permission is denied, this is the error reported, as required by the 97 // spec. 98 if (m_fatalError) 99 return; 100 101 m_fatalError = error; 102 // An existing timer may not have a zero timeout. 103 m_timer.stop(); 104 m_timer.startOneShot(0); 105} 106 107void Geolocation::GeoNotifier::setUseCachedPosition() 108{ 109 m_useCachedPosition = true; 110 m_timer.startOneShot(0); 111} 112 113bool Geolocation::GeoNotifier::hasZeroTimeout() const 114{ 115 return m_options->hasTimeout() && m_options->timeout() == 0; 116} 117 118void Geolocation::GeoNotifier::runSuccessCallback(Geoposition* position) 119{ 120 // If we are here and the Geolocation permission is not approved, something has 121 // gone horribly wrong. 122 if (!m_geolocation->isAllowed()) 123 CRASH(); 124 125 m_successCallback->handleEvent(position); 126} 127 128void Geolocation::GeoNotifier::runErrorCallback(PositionError* error) 129{ 130 if (m_errorCallback) 131 m_errorCallback->handleEvent(error); 132} 133 134void Geolocation::GeoNotifier::startTimerIfNeeded() 135{ 136 if (m_options->hasTimeout()) 137 m_timer.startOneShot(m_options->timeout() / 1000.0); 138} 139 140void Geolocation::GeoNotifier::stopTimer() 141{ 142 m_timer.stop(); 143} 144 145void Geolocation::GeoNotifier::timerFired(Timer<GeoNotifier>&) 146{ 147 m_timer.stop(); 148 149 // Protect this GeoNotifier object, since it 150 // could be deleted by a call to clearWatch in a callback. 151 Ref<GeoNotifier> protect(*this); 152 153 // Test for fatal error first. This is required for the case where the Frame is 154 // disconnected and requests are cancelled. 155 if (m_fatalError) { 156 runErrorCallback(m_fatalError.get()); 157 // This will cause this notifier to be deleted. 158 m_geolocation->fatalErrorOccurred(this); 159 return; 160 } 161 162 if (m_useCachedPosition) { 163 // Clear the cached position flag in case this is a watch request, which 164 // will continue to run. 165 m_useCachedPosition = false; 166 m_geolocation->requestUsesCachedPosition(this); 167 return; 168 } 169 170 if (m_errorCallback) { 171 RefPtr<PositionError> error = PositionError::create(PositionError::TIMEOUT, ASCIILiteral("Timeout expired")); 172 m_errorCallback->handleEvent(error.get()); 173 } 174 m_geolocation->requestTimedOut(this); 175} 176 177bool Geolocation::Watchers::add(int id, PassRefPtr<GeoNotifier> prpNotifier) 178{ 179 ASSERT(id > 0); 180 RefPtr<GeoNotifier> notifier = prpNotifier; 181 182 if (!m_idToNotifierMap.add(id, notifier.get()).isNewEntry) 183 return false; 184 m_notifierToIdMap.set(notifier.release(), id); 185 return true; 186} 187 188Geolocation::GeoNotifier* Geolocation::Watchers::find(int id) 189{ 190 ASSERT(id > 0); 191 return m_idToNotifierMap.get(id); 192} 193 194void Geolocation::Watchers::remove(int id) 195{ 196 ASSERT(id > 0); 197 if (auto notifier = m_idToNotifierMap.take(id)) 198 m_notifierToIdMap.remove(notifier); 199} 200 201void Geolocation::Watchers::remove(GeoNotifier* notifier) 202{ 203 if (auto identifier = m_notifierToIdMap.take(notifier)) 204 m_idToNotifierMap.remove(identifier); 205} 206 207bool Geolocation::Watchers::contains(GeoNotifier* notifier) const 208{ 209 return m_notifierToIdMap.contains(notifier); 210} 211 212void Geolocation::Watchers::clear() 213{ 214 m_idToNotifierMap.clear(); 215 m_notifierToIdMap.clear(); 216} 217 218bool Geolocation::Watchers::isEmpty() const 219{ 220 return m_idToNotifierMap.isEmpty(); 221} 222 223void Geolocation::Watchers::getNotifiersVector(GeoNotifierVector& copy) const 224{ 225 copyValuesToVector(m_idToNotifierMap, copy); 226} 227 228PassRef<Geolocation> Geolocation::create(ScriptExecutionContext* context) 229{ 230 auto geolocation = adoptRef(*new Geolocation(context)); 231 geolocation.get().suspendIfNeeded(); 232 return geolocation; 233} 234 235Geolocation::Geolocation(ScriptExecutionContext* context) 236 : ActiveDOMObject(context) 237 , m_allowGeolocation(Unknown) 238#if PLATFORM(IOS) 239 , m_isSuspended(false) 240 , m_hasChangedPosition(false) 241 , m_resumeTimer(this, &Geolocation::resumeTimerFired) 242#endif 243{ 244} 245 246Geolocation::~Geolocation() 247{ 248 ASSERT(m_allowGeolocation != InProgress); 249} 250 251Document* Geolocation::document() const 252{ 253 return toDocument(scriptExecutionContext()); 254} 255 256Frame* Geolocation::frame() const 257{ 258 return document() ? document()->frame() : 0; 259} 260 261Page* Geolocation::page() const 262{ 263 return document() ? document()->page() : 0; 264} 265 266#if PLATFORM(IOS) 267bool Geolocation::canSuspend() const 268{ 269 return !hasListeners(); 270} 271 272void Geolocation::suspend(ReasonForSuspension reason) 273{ 274 // Allow pages that no longer have listeners to enter the page cache. 275 // Have them stop updating and reset geolocation permissions when the page is resumed. 276 if (reason == ActiveDOMObject::DocumentWillBecomeInactive) { 277 ASSERT(!hasListeners()); 278 stop(); 279 m_resetOnResume = true; 280 } 281 282 // Suspend GeoNotifier timeout timers. 283 if (hasListeners()) 284 stopTimers(); 285 286 m_isSuspended = true; 287 m_resumeTimer.stop(); 288 ActiveDOMObject::suspend(reason); 289} 290 291void Geolocation::resume() 292{ 293 ASSERT(WebThreadIsLockedOrDisabled()); 294 ActiveDOMObject::resume(); 295 296 if (!m_resumeTimer.isActive()) 297 m_resumeTimer.startOneShot(0); 298} 299 300void Geolocation::resumeTimerFired(Timer<Geolocation>&) 301{ 302 m_isSuspended = false; 303 304 if (m_resetOnResume) { 305 resetAllGeolocationPermission(); 306 m_resetOnResume = false; 307 } 308 309 // Resume GeoNotifier timeout timers. 310 if (hasListeners()) { 311 GeoNotifierSet::const_iterator end = m_oneShots.end(); 312 for (GeoNotifierSet::const_iterator it = m_oneShots.begin(); it != end; ++it) 313 (*it)->startTimerIfNeeded(); 314 GeoNotifierVector watcherCopy; 315 m_watchers.getNotifiersVector(watcherCopy); 316 for (size_t i = 0; i < watcherCopy.size(); ++i) 317 watcherCopy[i]->startTimerIfNeeded(); 318 } 319 320 if ((isAllowed() || isDenied()) && !m_pendingForPermissionNotifiers.isEmpty()) { 321 // The pending permission was granted while the object was suspended. 322 setIsAllowed(isAllowed()); 323 ASSERT(!m_hasChangedPosition); 324 ASSERT(!m_errorWaitingForResume); 325 return; 326 } 327 328 if (isDenied() && hasListeners()) { 329 // The permission was revoked while the object was suspended. 330 setIsAllowed(false); 331 return; 332 } 333 334 if (m_hasChangedPosition) { 335 positionChanged(); 336 m_hasChangedPosition = false; 337 } 338 339 if (m_errorWaitingForResume) { 340 handleError(m_errorWaitingForResume.get()); 341 m_errorWaitingForResume = nullptr; 342 } 343} 344 345void Geolocation::resetAllGeolocationPermission() 346{ 347 if (m_isSuspended) { 348 m_resetOnResume = true; 349 return; 350 } 351 352 if (m_allowGeolocation == InProgress) { 353 Page* page = this->page(); 354 if (page) 355 GeolocationController::from(page)->cancelPermissionRequest(this); 356 357 // This return is not technically correct as GeolocationController::cancelPermissionRequest() should have cleared the active request. 358 // 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. :( 359 return; 360 } 361 362 // 1) Reset our own state. 363 stopUpdating(); 364 m_allowGeolocation = Unknown; 365 m_hasChangedPosition = false; 366 m_errorWaitingForResume = nullptr; 367 368 // 2) Request new permission for the active notifiers. 369 stopTimers(); 370 371 // Go over the one shot and re-request permission. 372 GeoNotifierSet::iterator end = m_oneShots.end(); 373 for (GeoNotifierSet::iterator it = m_oneShots.begin(); it != end; ++it) 374 startRequest((*it).get()); 375 // Go over the watchers and re-request permission. 376 GeoNotifierVector watcherCopy; 377 m_watchers.getNotifiersVector(watcherCopy); 378 for (size_t i = 0; i < watcherCopy.size(); ++i) 379 startRequest(watcherCopy[i].get()); 380} 381#endif // PLATFORM(IOS) 382 383void Geolocation::stop() 384{ 385 Page* page = this->page(); 386 if (page && m_allowGeolocation == InProgress) 387 GeolocationController::from(page)->cancelPermissionRequest(this); 388 // The frame may be moving to a new page and we want to get the permissions from the new page's client. 389 m_allowGeolocation = Unknown; 390 cancelAllRequests(); 391 stopUpdating(); 392#if PLATFORM(IOS) 393 m_hasChangedPosition = false; 394 m_errorWaitingForResume = nullptr; 395#endif // PLATFORM(IOS) 396 m_pendingForPermissionNotifiers.clear(); 397} 398 399Geoposition* Geolocation::lastPosition() 400{ 401 Page* page = this->page(); 402 if (!page) 403 return 0; 404 405 m_lastPosition = createGeoposition(GeolocationController::from(page)->lastPosition()); 406 407 return m_lastPosition.get(); 408} 409 410void Geolocation::getCurrentPosition(PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options) 411{ 412 if (!frame()) 413 return; 414 415 RefPtr<GeoNotifier> notifier = GeoNotifier::create(this, successCallback, errorCallback, options); 416 startRequest(notifier.get()); 417 418 m_oneShots.add(notifier); 419} 420 421int Geolocation::watchPosition(PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options) 422{ 423 if (!frame()) 424 return 0; 425 426 RefPtr<GeoNotifier> notifier = GeoNotifier::create(this, successCallback, errorCallback, options); 427 startRequest(notifier.get()); 428 429 int watchID; 430 // Keep asking for the next id until we're given one that we don't already have. 431 do { 432 watchID = m_scriptExecutionContext->circularSequentialID(); 433 } while (!m_watchers.add(watchID, notifier)); 434 return watchID; 435} 436 437void Geolocation::startRequest(GeoNotifier *notifier) 438{ 439 // Check whether permissions have already been denied. Note that if this is the case, 440 // the permission state can not change again in the lifetime of this page. 441 if (isDenied()) 442 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, ASCIILiteral(permissionDeniedErrorMessage))); 443 else if (haveSuitableCachedPosition(notifier->options())) 444 notifier->setUseCachedPosition(); 445 else if (notifier->hasZeroTimeout()) 446 notifier->startTimerIfNeeded(); 447 else if (!isAllowed()) { 448 // if we don't yet have permission, request for permission before calling startUpdating() 449 m_pendingForPermissionNotifiers.add(notifier); 450 requestPermission(); 451 } else if (startUpdating(notifier)) 452 notifier->startTimerIfNeeded(); 453 else 454 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, ASCIILiteral(failedToStartServiceErrorMessage))); 455} 456 457void Geolocation::fatalErrorOccurred(Geolocation::GeoNotifier* notifier) 458{ 459 // This request has failed fatally. Remove it from our lists. 460 m_oneShots.remove(notifier); 461 m_watchers.remove(notifier); 462 463 if (!hasListeners()) 464 stopUpdating(); 465} 466 467void Geolocation::requestUsesCachedPosition(GeoNotifier* notifier) 468{ 469 // This is called asynchronously, so the permissions could have been denied 470 // since we last checked in startRequest. 471 if (isDenied()) { 472 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, ASCIILiteral(permissionDeniedErrorMessage))); 473 return; 474 } 475 476 m_requestsAwaitingCachedPosition.add(notifier); 477 478 // If permissions are allowed, make the callback 479 if (isAllowed()) { 480 makeCachedPositionCallbacks(); 481 return; 482 } 483 484 // Request permissions, which may be synchronous or asynchronous. 485 requestPermission(); 486} 487 488void Geolocation::makeCachedPositionCallbacks() 489{ 490 // All modifications to m_requestsAwaitingCachedPosition are done 491 // asynchronously, so we don't need to worry about it being modified from 492 // the callbacks. 493 GeoNotifierSet::const_iterator end = m_requestsAwaitingCachedPosition.end(); 494 for (GeoNotifierSet::const_iterator iter = m_requestsAwaitingCachedPosition.begin(); iter != end; ++iter) { 495 GeoNotifier* notifier = iter->get(); 496 notifier->runSuccessCallback(lastPosition()); 497 498 // If this is a one-shot request, stop it. Otherwise, if the watch still 499 // exists, start the service to get updates. 500 if (!m_oneShots.remove(notifier) && m_watchers.contains(notifier)) { 501 if (notifier->hasZeroTimeout() || startUpdating(notifier)) 502 notifier->startTimerIfNeeded(); 503 else 504 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, ASCIILiteral(failedToStartServiceErrorMessage))); 505 } 506 } 507 508 m_requestsAwaitingCachedPosition.clear(); 509 510 if (!hasListeners()) 511 stopUpdating(); 512} 513 514void Geolocation::requestTimedOut(GeoNotifier* notifier) 515{ 516 // If this is a one-shot request, stop it. 517 m_oneShots.remove(notifier); 518 519 if (!hasListeners()) 520 stopUpdating(); 521} 522 523bool Geolocation::haveSuitableCachedPosition(PositionOptions* options) 524{ 525 Geoposition* cachedPosition = lastPosition(); 526 if (!cachedPosition) 527 return false; 528 if (!options->hasMaximumAge()) 529 return true; 530 if (!options->maximumAge()) 531 return false; 532 DOMTimeStamp currentTimeMillis = convertSecondsToDOMTimeStamp(currentTime()); 533 return cachedPosition->timestamp() > currentTimeMillis - options->maximumAge(); 534} 535 536void Geolocation::clearWatch(int watchID) 537{ 538 if (watchID <= 0) 539 return; 540 541 if (GeoNotifier* notifier = m_watchers.find(watchID)) 542 m_pendingForPermissionNotifiers.remove(notifier); 543 m_watchers.remove(watchID); 544 545 if (!hasListeners()) 546 stopUpdating(); 547} 548 549void Geolocation::setIsAllowed(bool allowed) 550{ 551 // Protect the Geolocation object from garbage collection during a callback. 552 Ref<Geolocation> protect(*this); 553 554 // This may be due to either a new position from the service, or a cached 555 // position. 556 m_allowGeolocation = allowed ? Yes : No; 557 558#if PLATFORM(IOS) 559 if (m_isSuspended) 560 return; 561#endif 562 563 // Permission request was made during the startRequest process 564 if (!m_pendingForPermissionNotifiers.isEmpty()) { 565 handlePendingPermissionNotifiers(); 566 m_pendingForPermissionNotifiers.clear(); 567 return; 568 } 569 570 if (!isAllowed()) { 571 RefPtr<PositionError> error = PositionError::create(PositionError::PERMISSION_DENIED, ASCIILiteral(permissionDeniedErrorMessage)); 572 error->setIsFatal(true); 573 handleError(error.get()); 574 m_requestsAwaitingCachedPosition.clear(); 575#if PLATFORM(IOS) 576 m_hasChangedPosition = false; 577 m_errorWaitingForResume = nullptr; 578#endif 579 580 return; 581 } 582 583 // If the service has a last position, use it to call back for all requests. 584 // If any of the requests are waiting for permission for a cached position, 585 // the position from the service will be at least as fresh. 586 if (lastPosition()) 587 makeSuccessCallbacks(); 588 else 589 makeCachedPositionCallbacks(); 590} 591 592void Geolocation::sendError(GeoNotifierVector& notifiers, PositionError* error) 593{ 594 GeoNotifierVector::const_iterator end = notifiers.end(); 595 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) { 596 RefPtr<GeoNotifier> notifier = *it; 597 598 notifier->runErrorCallback(error); 599 } 600} 601 602void Geolocation::sendPosition(GeoNotifierVector& notifiers, Geoposition* position) 603{ 604 GeoNotifierVector::const_iterator end = notifiers.end(); 605 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) 606 (*it)->runSuccessCallback(position); 607} 608 609void Geolocation::stopTimer(GeoNotifierVector& notifiers) 610{ 611 GeoNotifierVector::const_iterator end = notifiers.end(); 612 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) 613 (*it)->stopTimer(); 614} 615 616void Geolocation::stopTimersForOneShots() 617{ 618 GeoNotifierVector copy; 619 copyToVector(m_oneShots, copy); 620 621 stopTimer(copy); 622} 623 624void Geolocation::stopTimersForWatchers() 625{ 626 GeoNotifierVector copy; 627 m_watchers.getNotifiersVector(copy); 628 629 stopTimer(copy); 630} 631 632void Geolocation::stopTimers() 633{ 634 stopTimersForOneShots(); 635 stopTimersForWatchers(); 636} 637 638void Geolocation::cancelRequests(GeoNotifierVector& notifiers) 639{ 640 GeoNotifierVector::const_iterator end = notifiers.end(); 641 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) 642 (*it)->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, ASCIILiteral(framelessDocumentErrorMessage))); 643} 644 645void Geolocation::cancelAllRequests() 646{ 647 GeoNotifierVector copy; 648 copyToVector(m_oneShots, copy); 649 cancelRequests(copy); 650 m_watchers.getNotifiersVector(copy); 651 cancelRequests(copy); 652} 653 654void Geolocation::extractNotifiersWithCachedPosition(GeoNotifierVector& notifiers, GeoNotifierVector* cached) 655{ 656 GeoNotifierVector nonCached; 657 GeoNotifierVector::iterator end = notifiers.end(); 658 for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) { 659 GeoNotifier* notifier = it->get(); 660 if (notifier->useCachedPosition()) { 661 if (cached) 662 cached->append(notifier); 663 } else 664 nonCached.append(notifier); 665 } 666 notifiers.swap(nonCached); 667} 668 669void Geolocation::copyToSet(const GeoNotifierVector& src, GeoNotifierSet& dest) 670{ 671 GeoNotifierVector::const_iterator end = src.end(); 672 for (GeoNotifierVector::const_iterator it = src.begin(); it != end; ++it) { 673 GeoNotifier* notifier = it->get(); 674 dest.add(notifier); 675 } 676} 677 678void Geolocation::handleError(PositionError* error) 679{ 680 ASSERT(error); 681 682 GeoNotifierVector oneShotsCopy; 683 copyToVector(m_oneShots, oneShotsCopy); 684 685 GeoNotifierVector watchersCopy; 686 m_watchers.getNotifiersVector(watchersCopy); 687 688 // Clear the lists before we make the callbacks, to avoid clearing notifiers 689 // added by calls to Geolocation methods from the callbacks, and to prevent 690 // further callbacks to these notifiers. 691 GeoNotifierVector oneShotsWithCachedPosition; 692 m_oneShots.clear(); 693 if (error->isFatal()) 694 m_watchers.clear(); 695 else { 696 // Don't send non-fatal errors to notifiers due to receive a cached position. 697 extractNotifiersWithCachedPosition(oneShotsCopy, &oneShotsWithCachedPosition); 698 extractNotifiersWithCachedPosition(watchersCopy, 0); 699 } 700 701 sendError(oneShotsCopy, error); 702 sendError(watchersCopy, error); 703 704 // hasListeners() doesn't distinguish between notifiers due to receive a 705 // cached position and those requiring a fresh position. Perform the check 706 // before restoring the notifiers below. 707 if (!hasListeners()) 708 stopUpdating(); 709 710 // Maintain a reference to the cached notifiers until their timer fires. 711 copyToSet(oneShotsWithCachedPosition, m_oneShots); 712} 713 714void Geolocation::requestPermission() 715{ 716 if (m_allowGeolocation > Unknown) 717 return; 718 719 Page* page = this->page(); 720 if (!page) 721 return; 722 723 m_allowGeolocation = InProgress; 724 725 // Ask the embedder: it maintains the geolocation challenge policy itself. 726 GeolocationController::from(page)->requestPermission(this); 727} 728 729void Geolocation::makeSuccessCallbacks() 730{ 731 ASSERT(lastPosition()); 732 ASSERT(isAllowed()); 733 734 GeoNotifierVector oneShotsCopy; 735 copyToVector(m_oneShots, oneShotsCopy); 736 737 GeoNotifierVector watchersCopy; 738 m_watchers.getNotifiersVector(watchersCopy); 739 740 // Clear the lists before we make the callbacks, to avoid clearing notifiers 741 // added by calls to Geolocation methods from the callbacks, and to prevent 742 // further callbacks to these notifiers. 743 m_oneShots.clear(); 744 745 sendPosition(oneShotsCopy, lastPosition()); 746 sendPosition(watchersCopy, lastPosition()); 747 748 if (!hasListeners()) 749 stopUpdating(); 750} 751 752void Geolocation::positionChanged() 753{ 754 ASSERT(isAllowed()); 755 756 // Stop all currently running timers. 757 stopTimers(); 758 759#if PLATFORM(IOS) 760 if (m_isSuspended) { 761 m_hasChangedPosition = true; 762 return; 763 } 764#endif 765 766 makeSuccessCallbacks(); 767} 768 769void Geolocation::setError(GeolocationError* error) 770{ 771#if PLATFORM(IOS) 772 if (m_isSuspended) { 773 m_errorWaitingForResume = createPositionError(error); 774 return; 775 } 776#endif 777 RefPtr<PositionError> positionError = createPositionError(error); 778 handleError(positionError.get()); 779} 780 781bool Geolocation::startUpdating(GeoNotifier* notifier) 782{ 783 Page* page = this->page(); 784 if (!page) 785 return false; 786 787 GeolocationController::from(page)->addObserver(this, notifier->options()->enableHighAccuracy()); 788 return true; 789} 790 791void Geolocation::stopUpdating() 792{ 793 Page* page = this->page(); 794 if (!page) 795 return; 796 797 GeolocationController::from(page)->removeObserver(this); 798} 799 800void Geolocation::handlePendingPermissionNotifiers() 801{ 802 // While we iterate through the list, we need not worry about list being modified as the permission 803 // is already set to Yes/No and no new listeners will be added to the pending list 804 GeoNotifierSet::const_iterator end = m_pendingForPermissionNotifiers.end(); 805 for (GeoNotifierSet::const_iterator iter = m_pendingForPermissionNotifiers.begin(); iter != end; ++iter) { 806 GeoNotifier* notifier = iter->get(); 807 808 if (isAllowed()) { 809 // start all pending notification requests as permission granted. 810 // The notifier is always ref'ed by m_oneShots or m_watchers. 811 if (startUpdating(notifier)) 812 notifier->startTimerIfNeeded(); 813 else 814 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, ASCIILiteral(failedToStartServiceErrorMessage))); 815 } else 816 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, ASCIILiteral(permissionDeniedErrorMessage))); 817 } 818} 819 820} // namespace WebCore 821 822#endif // ENABLE(GEOLOCATION) 823