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