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