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
38namespace blink {
39
40static const char permissionDeniedErrorMessage[] = "User denied Geolocation";
41static const char failedToStartServiceErrorMessage[] = "Failed to start Geolocation service";
42static const char framelessDocumentErrorMessage[] = "Geolocation cannot be used in frameless documents";
43
44static 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
64static 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
79Geolocation* Geolocation::create(ExecutionContext* context)
80{
81    Geolocation* geolocation = new Geolocation(context);
82    geolocation->suspendIfNeeded();
83    return geolocation;
84}
85
86Geolocation::Geolocation(ExecutionContext* context)
87    : ActiveDOMObject(context)
88    , m_geolocationPermission(PermissionUnknown)
89{
90}
91
92Geolocation::~Geolocation()
93{
94    ASSERT(m_geolocationPermission != PermissionRequested);
95}
96
97void Geolocation::trace(Visitor* visitor)
98{
99    visitor->trace(m_oneShots);
100    visitor->trace(m_watchers);
101    visitor->trace(m_pendingForPermissionNotifiers);
102    visitor->trace(m_lastPosition);
103    visitor->trace(m_requestsAwaitingCachedPosition);
104}
105
106Document* Geolocation::document() const
107{
108    return toDocument(executionContext());
109}
110
111LocalFrame* Geolocation::frame() const
112{
113    return document() ? document()->frame() : 0;
114}
115
116void Geolocation::stop()
117{
118    LocalFrame* frame = this->frame();
119    if (frame && m_geolocationPermission == PermissionRequested)
120        GeolocationController::from(frame)->cancelPermissionRequest(this);
121
122    // The frame may be moving to a new page and we want to get the permissions from the new page's client.
123    m_geolocationPermission = PermissionUnknown;
124    cancelAllRequests();
125    stopUpdating();
126    m_pendingForPermissionNotifiers.clear();
127}
128
129Geoposition* Geolocation::lastPosition()
130{
131    LocalFrame* frame = this->frame();
132    if (!frame)
133        return 0;
134
135    m_lastPosition = createGeoposition(GeolocationController::from(frame)->lastPosition());
136
137    return m_lastPosition.get();
138}
139
140void Geolocation::getCurrentPosition(PositionCallback* successCallback, PositionErrorCallback* errorCallback, const Dictionary& options)
141{
142    if (!frame())
143        return;
144
145    GeoNotifier* notifier = GeoNotifier::create(this, successCallback, errorCallback, PositionOptions::create(options));
146    startRequest(notifier);
147
148    m_oneShots.add(notifier);
149}
150
151int Geolocation::watchPosition(PositionCallback* successCallback, PositionErrorCallback* errorCallback, const Dictionary& options)
152{
153    if (!frame())
154        return 0;
155
156    GeoNotifier* notifier = GeoNotifier::create(this, successCallback, errorCallback, PositionOptions::create(options));
157    startRequest(notifier);
158
159    int watchID;
160    // Keep asking for the next id until we're given one that we don't already have.
161    do {
162        watchID = executionContext()->circularSequentialID();
163    } while (!m_watchers.add(watchID, notifier));
164    return watchID;
165}
166
167void Geolocation::startRequest(GeoNotifier *notifier)
168{
169    // Check whether permissions have already been denied. Note that if this is the case,
170    // the permission state can not change again in the lifetime of this page.
171    if (isDenied())
172        notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
173    else if (haveSuitableCachedPosition(notifier->options()))
174        notifier->setUseCachedPosition();
175    else if (!notifier->options()->timeout())
176        notifier->startTimer();
177    else if (!isAllowed()) {
178        // if we don't yet have permission, request for permission before calling startUpdating()
179        m_pendingForPermissionNotifiers.add(notifier);
180        requestPermission();
181    } else if (startUpdating(notifier))
182        notifier->startTimer();
183    else
184        notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
185}
186
187void Geolocation::fatalErrorOccurred(GeoNotifier* notifier)
188{
189    // This request has failed fatally. Remove it from our lists.
190    m_oneShots.remove(notifier);
191    m_watchers.remove(notifier);
192
193    if (!hasListeners())
194        stopUpdating();
195}
196
197void Geolocation::requestUsesCachedPosition(GeoNotifier* notifier)
198{
199    // This is called asynchronously, so the permissions could have been denied
200    // since we last checked in startRequest.
201    if (isDenied()) {
202        notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
203        return;
204    }
205
206    m_requestsAwaitingCachedPosition.add(notifier);
207
208    // If permissions are allowed, make the callback
209    if (isAllowed()) {
210        makeCachedPositionCallbacks();
211        return;
212    }
213
214    // Request permissions, which may be synchronous or asynchronous.
215    requestPermission();
216}
217
218void Geolocation::makeCachedPositionCallbacks()
219{
220    // All modifications to m_requestsAwaitingCachedPosition are done
221    // asynchronously, so we don't need to worry about it being modified from
222    // the callbacks.
223    GeoNotifierSet::const_iterator end = m_requestsAwaitingCachedPosition.end();
224    for (GeoNotifierSet::const_iterator iter = m_requestsAwaitingCachedPosition.begin(); iter != end; ++iter) {
225        GeoNotifier* notifier = iter->get();
226        notifier->runSuccessCallback(lastPosition());
227
228        // If this is a one-shot request, stop it. Otherwise, if the watch still
229        // exists, start the service to get updates.
230        if (m_oneShots.contains(notifier))
231            m_oneShots.remove(notifier);
232        else if (m_watchers.contains(notifier)) {
233            if (!notifier->options()->timeout() || startUpdating(notifier))
234                notifier->startTimer();
235            else
236                notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
237        }
238    }
239
240    m_requestsAwaitingCachedPosition.clear();
241
242    if (!hasListeners())
243        stopUpdating();
244}
245
246void Geolocation::requestTimedOut(GeoNotifier* notifier)
247{
248    // If this is a one-shot request, stop it.
249    m_oneShots.remove(notifier);
250
251    if (!hasListeners())
252        stopUpdating();
253}
254
255bool Geolocation::haveSuitableCachedPosition(PositionOptions* options)
256{
257    Geoposition* cachedPosition = lastPosition();
258    if (!cachedPosition)
259        return false;
260    if (!options->maximumAge())
261        return false;
262    DOMTimeStamp currentTimeMillis = convertSecondsToDOMTimeStamp(currentTime());
263    return cachedPosition->timestamp() > currentTimeMillis - options->maximumAge();
264}
265
266void Geolocation::clearWatch(int watchID)
267{
268    if (watchID <= 0)
269        return;
270
271    if (GeoNotifier* notifier = m_watchers.find(watchID))
272        m_pendingForPermissionNotifiers.remove(notifier);
273    m_watchers.remove(watchID);
274
275    if (!hasListeners())
276        stopUpdating();
277}
278
279void Geolocation::setIsAllowed(bool allowed)
280{
281    // This may be due to either a new position from the service, or a cached position.
282    m_geolocationPermission = allowed ? PermissionAllowed : PermissionDenied;
283
284    // Permission request was made during the startRequest process
285    if (!m_pendingForPermissionNotifiers.isEmpty()) {
286        handlePendingPermissionNotifiers();
287        m_pendingForPermissionNotifiers.clear();
288        return;
289    }
290
291    if (!isAllowed()) {
292        PositionError* error = PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage);
293        error->setIsFatal(true);
294        handleError(error);
295        m_requestsAwaitingCachedPosition.clear();
296        return;
297    }
298
299    // If the service has a last position, use it to call back for all requests.
300    // If any of the requests are waiting for permission for a cached position,
301    // the position from the service will be at least as fresh.
302    if (lastPosition())
303        makeSuccessCallbacks();
304    else
305        makeCachedPositionCallbacks();
306}
307
308void Geolocation::sendError(GeoNotifierVector& notifiers, PositionError* error)
309{
310    GeoNotifierVector::const_iterator end = notifiers.end();
311    for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
312        (*it)->runErrorCallback(error);
313}
314
315void Geolocation::sendPosition(GeoNotifierVector& notifiers, Geoposition* position)
316{
317    GeoNotifierVector::const_iterator end = notifiers.end();
318    for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
319        (*it)->runSuccessCallback(position);
320}
321
322void Geolocation::stopTimer(GeoNotifierVector& notifiers)
323{
324    GeoNotifierVector::const_iterator end = notifiers.end();
325    for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
326        (*it)->stopTimer();
327}
328
329void Geolocation::stopTimersForOneShots()
330{
331    GeoNotifierVector copy;
332    copyToVector(m_oneShots, copy);
333
334    stopTimer(copy);
335}
336
337void Geolocation::stopTimersForWatchers()
338{
339    GeoNotifierVector copy;
340    m_watchers.getNotifiersVector(copy);
341
342    stopTimer(copy);
343}
344
345void Geolocation::stopTimers()
346{
347    stopTimersForOneShots();
348    stopTimersForWatchers();
349}
350
351void Geolocation::cancelRequests(GeoNotifierVector& notifiers)
352{
353    GeoNotifierVector::const_iterator end = notifiers.end();
354    for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it)
355        (*it)->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, framelessDocumentErrorMessage));
356}
357
358void Geolocation::cancelAllRequests()
359{
360    GeoNotifierVector copy;
361    copyToVector(m_oneShots, copy);
362    cancelRequests(copy);
363    m_watchers.getNotifiersVector(copy);
364    cancelRequests(copy);
365}
366
367void Geolocation::extractNotifiersWithCachedPosition(GeoNotifierVector& notifiers, GeoNotifierVector* cached)
368{
369    GeoNotifierVector nonCached;
370    GeoNotifierVector::iterator end = notifiers.end();
371    for (GeoNotifierVector::const_iterator it = notifiers.begin(); it != end; ++it) {
372        GeoNotifier* notifier = it->get();
373        if (notifier->useCachedPosition()) {
374            if (cached)
375                cached->append(notifier);
376        } else
377            nonCached.append(notifier);
378    }
379    notifiers.swap(nonCached);
380}
381
382void Geolocation::copyToSet(const GeoNotifierVector& src, GeoNotifierSet& dest)
383{
384     GeoNotifierVector::const_iterator end = src.end();
385     for (GeoNotifierVector::const_iterator it = src.begin(); it != end; ++it) {
386         GeoNotifier* notifier = it->get();
387         dest.add(notifier);
388     }
389}
390
391void Geolocation::handleError(PositionError* error)
392{
393    ASSERT(error);
394
395    GeoNotifierVector oneShotsCopy;
396    copyToVector(m_oneShots, oneShotsCopy);
397
398    GeoNotifierVector watchersCopy;
399    m_watchers.getNotifiersVector(watchersCopy);
400
401    // Clear the lists before we make the callbacks, to avoid clearing notifiers
402    // added by calls to Geolocation methods from the callbacks, and to prevent
403    // further callbacks to these notifiers.
404    GeoNotifierVector oneShotsWithCachedPosition;
405    m_oneShots.clear();
406    if (error->isFatal())
407        m_watchers.clear();
408    else {
409        // Don't send non-fatal errors to notifiers due to receive a cached position.
410        extractNotifiersWithCachedPosition(oneShotsCopy, &oneShotsWithCachedPosition);
411        extractNotifiersWithCachedPosition(watchersCopy, 0);
412    }
413
414    sendError(oneShotsCopy, error);
415    sendError(watchersCopy, error);
416
417    // hasListeners() doesn't distinguish between notifiers due to receive a
418    // cached position and those requiring a fresh position. Perform the check
419    // before restoring the notifiers below.
420    if (!hasListeners())
421        stopUpdating();
422
423    // Maintain a reference to the cached notifiers until their timer fires.
424    copyToSet(oneShotsWithCachedPosition, m_oneShots);
425}
426
427void Geolocation::requestPermission()
428{
429    if (m_geolocationPermission != PermissionUnknown)
430        return;
431
432    LocalFrame* frame = this->frame();
433    if (!frame)
434        return;
435
436    m_geolocationPermission = PermissionRequested;
437
438    // Ask the embedder: it maintains the geolocation challenge policy itself.
439    GeolocationController::from(frame)->requestPermission(this);
440}
441
442void Geolocation::makeSuccessCallbacks()
443{
444    ASSERT(lastPosition());
445    ASSERT(isAllowed());
446
447    GeoNotifierVector oneShotsCopy;
448    copyToVector(m_oneShots, oneShotsCopy);
449
450    GeoNotifierVector watchersCopy;
451    m_watchers.getNotifiersVector(watchersCopy);
452
453    // Clear the lists before we make the callbacks, to avoid clearing notifiers
454    // added by calls to Geolocation methods from the callbacks, and to prevent
455    // further callbacks to these notifiers.
456    m_oneShots.clear();
457
458    // Also clear the set of notifiers waiting for a cached position. All the
459    // oneshots and watchers will receive a position now, and if they happen to
460    // be lingering in that set, avoid this bug: http://crbug.com/311876 .
461    m_requestsAwaitingCachedPosition.clear();
462
463    sendPosition(oneShotsCopy, lastPosition());
464    sendPosition(watchersCopy, lastPosition());
465
466    if (!hasListeners())
467        stopUpdating();
468}
469
470void Geolocation::positionChanged()
471{
472    ASSERT(isAllowed());
473
474    // Stop all currently running timers.
475    stopTimers();
476
477    makeSuccessCallbacks();
478}
479
480void Geolocation::setError(GeolocationError* error)
481{
482    handleError(createPositionError(error));
483}
484
485bool Geolocation::startUpdating(GeoNotifier* notifier)
486{
487    LocalFrame* frame = this->frame();
488    if (!frame)
489        return false;
490
491    GeolocationController::from(frame)->addObserver(this, notifier->options()->enableHighAccuracy());
492    return true;
493}
494
495void Geolocation::stopUpdating()
496{
497    LocalFrame* frame = this->frame();
498    if (!frame)
499        return;
500
501    GeolocationController::from(frame)->removeObserver(this);
502}
503
504void Geolocation::handlePendingPermissionNotifiers()
505{
506    // While we iterate through the list, we need not worry about list being modified as the permission
507    // is already set to Yes/No and no new listeners will be added to the pending list
508    GeoNotifierSet::const_iterator end = m_pendingForPermissionNotifiers.end();
509    for (GeoNotifierSet::const_iterator iter = m_pendingForPermissionNotifiers.begin(); iter != end; ++iter) {
510        GeoNotifier* notifier = iter->get();
511
512        if (isAllowed()) {
513            // start all pending notification requests as permission granted.
514            // The notifier is always ref'ed by m_oneShots or m_watchers.
515            if (startUpdating(notifier))
516                notifier->startTimer();
517            else
518                notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, failedToStartServiceErrorMessage));
519        } else {
520            notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
521        }
522    }
523}
524
525} // namespace blink
526