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