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