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