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