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 "Coordinates.h"
34#include "Document.h"
35#include "Frame.h"
36#include "GeoNotifier.h"
37#include "GeolocationController.h"
38#include "GeolocationError.h"
39#include "GeolocationPosition.h"
40#include "Geoposition.h"
41#include "Page.h"
42#include "PositionError.h"
43#include "SecurityOrigin.h"
44#include <wtf/CurrentTime.h>
45#include <wtf/Ref.h>
46
47namespace WebCore {
48
49static const char permissionDeniedErrorMessage[] = "User denied Geolocation";
50static const char failedToStartServiceErrorMessage[] = "Failed to start Geolocation service";
51static const char framelessDocumentErrorMessage[] = "Geolocation cannot be used in frameless documents";
52static const char originCannotRequestGeolocationErrorMessage[] = "Origin does not have permission to use Geolocation service";
53
54static RefPtr<Geoposition> createGeoposition(GeolocationPosition* position)
55{
56 if (!position)
57 return nullptr;
58
59 RefPtr<Coordinates> coordinates = Coordinates::create(position->latitude(), position->longitude(), position->canProvideAltitude(), position->altitude(),
60 position->accuracy(), position->canProvideAltitudeAccuracy(), position->altitudeAccuracy(),
61 position->canProvideHeading(), position->heading(), position->canProvideSpeed(), position->speed());
62 return Geoposition::create(coordinates.release(), convertSecondsToDOMTimeStamp(position->timestamp()));
63}
64
65static Ref<PositionError> createPositionError(GeolocationError* error)
66{
67 PositionError::ErrorCode code = PositionError::POSITION_UNAVAILABLE;
68 switch (error->code()) {
69 case GeolocationError::PermissionDenied:
70 code = PositionError::PERMISSION_DENIED;
71 break;
72 case GeolocationError::PositionUnavailable:
73 code = PositionError::POSITION_UNAVAILABLE;
74 break;
75 }
76
77 return PositionError::create(code, error->message());
78}
79
80bool Geolocation::Watchers::add(int id, RefPtr<GeoNotifier>&& notifier)
81{
82 ASSERT(id > 0);
83
84 if (!m_idToNotifierMap.add(id, notifier.get()).isNewEntry)
85 return false;
86 m_notifierToIdMap.set(WTFMove(notifier), id);
87 return true;
88}
89
90GeoNotifier* Geolocation::Watchers::find(int id)
91{
92 ASSERT(id > 0);
93 return m_idToNotifierMap.get(id);
94}
95
96void Geolocation::Watchers::remove(int id)
97{
98 ASSERT(id > 0);
99 if (auto notifier = m_idToNotifierMap.take(id))
100 m_notifierToIdMap.remove(notifier);
101}
102
103void Geolocation::Watchers::remove(GeoNotifier* notifier)
104{
105 if (auto identifier = m_notifierToIdMap.take(notifier))
106 m_idToNotifierMap.remove(identifier);
107}
108
109bool Geolocation::Watchers::contains(GeoNotifier* notifier) const
110{
111 return m_notifierToIdMap.contains(notifier);
112}
113
114void Geolocation::Watchers::clear()
115{
116 m_idToNotifierMap.clear();
117 m_notifierToIdMap.clear();
118}
119
120bool Geolocation::Watchers::isEmpty() const
121{
122 return m_idToNotifierMap.isEmpty();
123}
124
125void Geolocation::Watchers::getNotifiersVector(GeoNotifierVector& copy) const
126{
127 copyValuesToVector(m_idToNotifierMap, copy);
128}
129
130Ref<Geolocation> Geolocation::create(ScriptExecutionContext* context)
131{
132 auto geolocation = adoptRef(*new Geolocation(context));
133 geolocation.get().suspendIfNeeded();
134 return geolocation;
135}
136
137Geolocation::Geolocation(ScriptExecutionContext* context)
138 : ActiveDOMObject(context)
139 , m_allowGeolocation(Unknown)
140 , m_isSuspended(false)
141 , m_hasChangedPosition(false)
142 , m_resumeTimer(*this, &Geolocation::resumeTimerFired)
143{
144}
145
146Geolocation::~Geolocation()
147{
148 ASSERT(m_allowGeolocation != InProgress);
149}
150
151Document* Geolocation::document() const
152{
153 return downcast<Document>(scriptExecutionContext());
154}
155
156SecurityOrigin* Geolocation::securityOrigin() const
157{
158 return scriptExecutionContext()->securityOrigin();
159}
160
161Frame* Geolocation::frame() const
162{
163 return document() ? document()->frame() : nullptr;
164}
165
166Page* Geolocation::page() const
167{
168 return document() ? document()->page() : nullptr;
169}
170
171bool Geolocation::canSuspendForDocumentSuspension() const
172{
173 return true;
174}
175
176void Geolocation::suspend(ReasonForSuspension reason)
177{
178 if (reason == ActiveDOMObject::PageCache) {
179 stop();
180 m_resetOnResume = true;
181 }
182
183 // Suspend GeoNotifier timeout timers.
184 if (hasListeners())
185 stopTimers();
186
187 m_isSuspended = true;
188 m_resumeTimer.stop();
189 ActiveDOMObject::suspend(reason);
190}
191
192void Geolocation::resume()
193{
194#if USE(WEB_THREAD)
195 ASSERT(WebThreadIsLockedOrDisabled());
196#endif
197 ActiveDOMObject::resume();
198
199 if (!m_resumeTimer.isActive())
200 m_resumeTimer.startOneShot(0);
201}
202
203void Geolocation::resumeTimerFired()
204{
205 m_isSuspended = false;
206
207 if (m_resetOnResume) {
208 resetAllGeolocationPermission();
209 m_resetOnResume = false;
210 }
211
212 // Resume GeoNotifier timeout timers.
213 if (hasListeners()) {
214 for (auto& notifier : m_oneShots)
215 notifier->startTimerIfNeeded();
216 GeoNotifierVector watcherCopy;
217 m_watchers.getNotifiersVector(watcherCopy);
218 for (auto& watcher : watcherCopy)
219 watcher->startTimerIfNeeded();
220 }
221
222 if ((isAllowed() || isDenied()) && !m_pendingForPermissionNotifiers.isEmpty()) {
223 // The pending permission was granted while the object was suspended.
224 setIsAllowed(isAllowed());
225 ASSERT(!m_hasChangedPosition);
226 ASSERT(!m_errorWaitingForResume);
227 return;
228 }
229
230 if (isDenied() && hasListeners()) {
231 // The permission was revoked while the object was suspended.
232 setIsAllowed(false);
233 return;
234 }
235
236 if (m_hasChangedPosition) {
237 positionChanged();
238 m_hasChangedPosition = false;
239 }
240
241 if (m_errorWaitingForResume) {
242 handleError(m_errorWaitingForResume.get());
243 m_errorWaitingForResume = nullptr;
244 }
245}
246
247void Geolocation::resetAllGeolocationPermission()
248{
249 if (m_isSuspended) {
250 m_resetOnResume = true;
251 return;
252 }
253
254 if (m_allowGeolocation == InProgress) {
255 Page* page = this->page();
256 if (page)
257 GeolocationController::from(page)->cancelPermissionRequest(this);
258
259 // This return is not technically correct as GeolocationController::cancelPermissionRequest() should have cleared the active request.
260 // 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. :(
261 return;
262 }
263
264 // 1) Reset our own state.
265 stopUpdating();
266 m_allowGeolocation = Unknown;
267 m_hasChangedPosition = false;
268 m_errorWaitingForResume = nullptr;
269
270 // 2) Request new permission for the active notifiers.
271 stopTimers();
272
273 // Go over the one shot and re-request permission.
274 for (auto& notifier : m_oneShots)
275 startRequest(notifier.get());
276 // Go over the watchers and re-request permission.
277 GeoNotifierVector watcherCopy;
278 m_watchers.getNotifiersVector(watcherCopy);
279 for (auto& watcher : watcherCopy)
280 startRequest(watcher.get());
281}
282
283void Geolocation::stop()
284{
285 Page* page = this->page();
286 if (page && m_allowGeolocation == InProgress)
287 GeolocationController::from(page)->cancelPermissionRequest(this);
288 // The frame may be moving to a new page and we want to get the permissions from the new page's client.
289 m_allowGeolocation = Unknown;
290 cancelAllRequests();
291 stopUpdating();
292 m_hasChangedPosition = false;
293 m_errorWaitingForResume = nullptr;
294 m_pendingForPermissionNotifiers.clear();
295}
296
297const char* Geolocation::activeDOMObjectName() const
298{
299 return "Geolocation";
300}
301
302Geoposition* Geolocation::lastPosition()
303{
304 Page* page = this->page();
305 if (!page)
306 return 0;
307
308 m_lastPosition = createGeoposition(GeolocationController::from(page)->lastPosition());
309
310 return m_lastPosition.get();
311}
312
313void Geolocation::getCurrentPosition(RefPtr<PositionCallback>&& successCallback, RefPtr<PositionErrorCallback>&& errorCallback, RefPtr<PositionOptions>&& options)
314{
315 if (!frame())
316 return;
317
318 RefPtr<GeoNotifier> notifier = GeoNotifier::create(*this, WTFMove(successCallback), WTFMove(errorCallback), WTFMove(options));
319 startRequest(notifier.get());
320
321 m_oneShots.add(notifier);
322}
323
324int Geolocation::watchPosition(RefPtr<PositionCallback>&& successCallback, RefPtr<PositionErrorCallback>&& errorCallback, RefPtr<PositionOptions>&& options)
325{
326 if (!frame())
327 return 0;
328
329 RefPtr<GeoNotifier> notifier = GeoNotifier::create(*this, WTFMove(successCallback), WTFMove(errorCallback), WTFMove(options));
330 startRequest(notifier.get());
331
332 int watchID;
333 // Keep asking for the next id until we're given one that we don't already have.
334 do {
335 watchID = m_scriptExecutionContext->circularSequentialID();
336 } while (!m_watchers.add(watchID, WTFMove(notifier)));
337 return watchID;
338}
339
340void Geolocation::startRequest(GeoNotifier* notifier)
341{
342 if (!securityOrigin()->canRequestGeolocation()) {
343 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, ASCIILiteral(originCannotRequestGeolocationErrorMessage)));
344 return;
345 }
346
347 // Check whether permissions have already been denied. Note that if this is the case,
348 // the permission state can not change again in the lifetime of this page.
349 if (isDenied())
350 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, ASCIILiteral(permissionDeniedErrorMessage)));
351 else if (haveSuitableCachedPosition(notifier->options()))
352 notifier->setUseCachedPosition();
353 else if (notifier->hasZeroTimeout())
354 notifier->startTimerIfNeeded();
355 else if (!isAllowed()) {
356 // if we don't yet have permission, request for permission before calling startUpdating()
357 m_pendingForPermissionNotifiers.add(notifier);
358 requestPermission();
359 } else if (startUpdating(notifier))
360 notifier->startTimerIfNeeded();
361 else
362 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, ASCIILiteral(failedToStartServiceErrorMessage)));
363}
364
365void Geolocation::fatalErrorOccurred(GeoNotifier* notifier)
366{
367 // This request has failed fatally. Remove it from our lists.
368 m_oneShots.remove(notifier);
369 m_watchers.remove(notifier);
370
371 if (!hasListeners())
372 stopUpdating();
373}
374
375void Geolocation::requestUsesCachedPosition(GeoNotifier* notifier)
376{
377 // This is called asynchronously, so the permissions could have been denied
378 // since we last checked in startRequest.
379 if (isDenied()) {
380 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, ASCIILiteral(permissionDeniedErrorMessage)));
381 return;
382 }
383
384 m_requestsAwaitingCachedPosition.add(notifier);
385
386 // If permissions are allowed, make the callback
387 if (isAllowed()) {
388 makeCachedPositionCallbacks();
389 return;
390 }
391
392 // Request permissions, which may be synchronous or asynchronous.
393 requestPermission();
394}
395
396void Geolocation::makeCachedPositionCallbacks()
397{
398 // All modifications to m_requestsAwaitingCachedPosition are done
399 // asynchronously, so we don't need to worry about it being modified from
400 // the callbacks.
401 for (auto& notifier : m_requestsAwaitingCachedPosition) {
402 notifier->runSuccessCallback(lastPosition());
403
404 // If this is a one-shot request, stop it. Otherwise, if the watch still
405 // exists, start the service to get updates.
406 if (!m_oneShots.remove(notifier.get()) && m_watchers.contains(notifier.get())) {
407 if (notifier->hasZeroTimeout() || startUpdating(notifier.get()))
408 notifier->startTimerIfNeeded();
409 else
410 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, ASCIILiteral(failedToStartServiceErrorMessage)));
411 }
412 }
413
414 m_requestsAwaitingCachedPosition.clear();
415
416 if (!hasListeners())
417 stopUpdating();
418}
419
420void Geolocation::requestTimedOut(GeoNotifier* notifier)
421{
422 // If this is a one-shot request, stop it.
423 m_oneShots.remove(notifier);
424
425 if (!hasListeners())
426 stopUpdating();
427}
428
429bool Geolocation::haveSuitableCachedPosition(PositionOptions* options)
430{
431 Geoposition* cachedPosition = lastPosition();
432 if (!cachedPosition)
433 return false;
434 if (!options->hasMaximumAge())
435 return true;
436 if (!options->maximumAge())
437 return false;
438 DOMTimeStamp currentTimeMillis = convertSecondsToDOMTimeStamp(currentTime());
439 return cachedPosition->timestamp() > currentTimeMillis - options->maximumAge();
440}
441
442void Geolocation::clearWatch(int watchID)
443{
444 if (watchID <= 0)
445 return;
446
447 if (GeoNotifier* notifier = m_watchers.find(watchID))
448 m_pendingForPermissionNotifiers.remove(notifier);
449 m_watchers.remove(watchID);
450
451 if (!hasListeners())
452 stopUpdating();
453}
454
455void Geolocation::setIsAllowed(bool allowed)
456{
457 // Protect the Geolocation object from garbage collection during a callback.
458 Ref<Geolocation> protect(*this);
459
460 // This may be due to either a new position from the service, or a cached
461 // position.
462 m_allowGeolocation = allowed ? Yes : No;
463
464 if (m_isSuspended)
465 return;
466
467 // Permission request was made during the startRequest process
468 if (!m_pendingForPermissionNotifiers.isEmpty()) {
469 handlePendingPermissionNotifiers();
470 m_pendingForPermissionNotifiers.clear();
471 return;
472 }
473
474 if (!isAllowed()) {
475 RefPtr<PositionError> error = PositionError::create(PositionError::PERMISSION_DENIED, ASCIILiteral(permissionDeniedErrorMessage));
476 error->setIsFatal(true);
477 handleError(error.get());
478 m_requestsAwaitingCachedPosition.clear();
479 m_hasChangedPosition = false;
480 m_errorWaitingForResume = nullptr;
481
482 return;
483 }
484
485 // If the service has a last position, use it to call back for all requests.
486 // If any of the requests are waiting for permission for a cached position,
487 // the position from the service will be at least as fresh.
488 if (lastPosition())
489 makeSuccessCallbacks();
490 else
491 makeCachedPositionCallbacks();
492}
493
494void Geolocation::sendError(GeoNotifierVector& notifiers, PositionError* error)
495{
496 for (auto& notifier : notifiers)
497 notifier->runErrorCallback(error);
498}
499
500void Geolocation::sendPosition(GeoNotifierVector& notifiers, Geoposition* position)
501{
502 for (auto& notifier : notifiers)
503 notifier->runSuccessCallback(position);
504}
505
506void Geolocation::stopTimer(GeoNotifierVector& notifiers)
507{
508 for (auto& notifier : notifiers)
509 notifier->stopTimer();
510}
511
512void Geolocation::stopTimersForOneShots()
513{
514 GeoNotifierVector copy;
515 copyToVector(m_oneShots, copy);
516
517 stopTimer(copy);
518}
519
520void Geolocation::stopTimersForWatchers()
521{
522 GeoNotifierVector copy;
523 m_watchers.getNotifiersVector(copy);
524
525 stopTimer(copy);
526}
527
528void Geolocation::stopTimers()
529{
530 stopTimersForOneShots();
531 stopTimersForWatchers();
532}
533
534void Geolocation::cancelRequests(GeoNotifierVector& notifiers)
535{
536 for (auto& notifier : notifiers)
537 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, ASCIILiteral(framelessDocumentErrorMessage)));
538}
539
540void Geolocation::cancelAllRequests()
541{
542 GeoNotifierVector copy;
543 copyToVector(m_oneShots, copy);
544 cancelRequests(copy);
545 m_watchers.getNotifiersVector(copy);
546 cancelRequests(copy);
547}
548
549void Geolocation::extractNotifiersWithCachedPosition(GeoNotifierVector& notifiers, GeoNotifierVector* cached)
550{
551 GeoNotifierVector nonCached;
552 for (auto& notifier : notifiers) {
553 if (notifier->useCachedPosition()) {
554 if (cached)
555 cached->append(notifier.get());
556 } else
557 nonCached.append(notifier.get());
558 }
559 notifiers.swap(nonCached);
560}
561
562void Geolocation::copyToSet(const GeoNotifierVector& src, GeoNotifierSet& dest)
563{
564 for (auto& notifier : src)
565 dest.add(notifier.get());
566}
567
568void Geolocation::handleError(PositionError* error)
569{
570 ASSERT(error);
571
572 GeoNotifierVector oneShotsCopy;
573 copyToVector(m_oneShots, oneShotsCopy);
574
575 GeoNotifierVector watchersCopy;
576 m_watchers.getNotifiersVector(watchersCopy);
577
578 // Clear the lists before we make the callbacks, to avoid clearing notifiers
579 // added by calls to Geolocation methods from the callbacks, and to prevent
580 // further callbacks to these notifiers.
581 GeoNotifierVector oneShotsWithCachedPosition;
582 m_oneShots.clear();
583 if (error->isFatal())
584 m_watchers.clear();
585 else {
586 // Don't send non-fatal errors to notifiers due to receive a cached position.
587 extractNotifiersWithCachedPosition(oneShotsCopy, &oneShotsWithCachedPosition);
588 extractNotifiersWithCachedPosition(watchersCopy, 0);
589 }
590
591 sendError(oneShotsCopy, error);
592 sendError(watchersCopy, error);
593
594 // hasListeners() doesn't distinguish between notifiers due to receive a
595 // cached position and those requiring a fresh position. Perform the check
596 // before restoring the notifiers below.
597 if (!hasListeners())
598 stopUpdating();
599
600 // Maintain a reference to the cached notifiers until their timer fires.
601 copyToSet(oneShotsWithCachedPosition, m_oneShots);
602}
603
604void Geolocation::requestPermission()
605{
606 if (m_allowGeolocation > Unknown)
607 return;
608
609 Page* page = this->page();
610 if (!page)
611 return;
612
613 m_allowGeolocation = InProgress;
614
615 // Ask the embedder: it maintains the geolocation challenge policy itself.
616 GeolocationController::from(page)->requestPermission(this);
617}
618
619void Geolocation::makeSuccessCallbacks()
620{
621 ASSERT(lastPosition());
622 ASSERT(isAllowed());
623
624 GeoNotifierVector oneShotsCopy;
625 copyToVector(m_oneShots, oneShotsCopy);
626
627 GeoNotifierVector watchersCopy;
628 m_watchers.getNotifiersVector(watchersCopy);
629
630 // Clear the lists before we make the callbacks, to avoid clearing notifiers
631 // added by calls to Geolocation methods from the callbacks, and to prevent
632 // further callbacks to these notifiers.
633 m_oneShots.clear();
634
635 sendPosition(oneShotsCopy, lastPosition());
636 sendPosition(watchersCopy, lastPosition());
637
638 if (!hasListeners())
639 stopUpdating();
640}
641
642void Geolocation::positionChanged()
643{
644 ASSERT(isAllowed());
645
646 // Stop all currently running timers.
647 stopTimers();
648
649 if (m_isSuspended) {
650 m_hasChangedPosition = true;
651 return;
652 }
653
654 makeSuccessCallbacks();
655}
656
657void Geolocation::setError(GeolocationError* error)
658{
659 if (m_isSuspended) {
660 m_errorWaitingForResume = createPositionError(error);
661 return;
662 }
663 RefPtr<PositionError> positionError = createPositionError(error);
664 handleError(positionError.get());
665}
666
667bool Geolocation::startUpdating(GeoNotifier* notifier)
668{
669 Page* page = this->page();
670 if (!page)
671 return false;
672
673 GeolocationController::from(page)->addObserver(this, notifier->options()->enableHighAccuracy());
674 return true;
675}
676
677void Geolocation::stopUpdating()
678{
679 Page* page = this->page();
680 if (!page)
681 return;
682
683 GeolocationController::from(page)->removeObserver(this);
684}
685
686void Geolocation::handlePendingPermissionNotifiers()
687{
688 // While we iterate through the list, we need not worry about list being modified as the permission
689 // is already set to Yes/No and no new listeners will be added to the pending list
690 for (auto& notifier : m_pendingForPermissionNotifiers) {
691 if (isAllowed()) {
692 // start all pending notification requests as permission granted.
693 // The notifier is always ref'ed by m_oneShots or m_watchers.
694 if (startUpdating(notifier.get()))
695 notifier->startTimerIfNeeded();
696 else
697 notifier->setFatalError(PositionError::create(PositionError::POSITION_UNAVAILABLE, ASCIILiteral(failedToStartServiceErrorMessage)));
698 } else
699 notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, ASCIILiteral(permissionDeniedErrorMessage)));
700 }
701}
702
703} // namespace WebCore
704
705#endif // ENABLE(GEOLOCATION)
706