• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008, 2009 Apple Inc. All Rights Reserved.
3  * Copyright (C) 2009 Torch Mobile, Inc.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include "config.h"
28 #include "Geolocation.h"
29 
30 #include "Chrome.h"
31 #include "CurrentTime.h"
32 #include "Document.h"
33 #include "DOMWindow.h"
34 #include "EventNames.h"
35 #include "Frame.h"
36 #include "Page.h"
37 #include "SQLiteDatabase.h"
38 #include "SQLiteStatement.h"
39 #include "SQLiteTransaction.h"
40 #include "SQLValue.h"
41 
42 namespace WebCore {
43 
44 static const char* permissionDeniedErrorMessage = "User denied Geolocation";
45 
GeoNotifier(Geolocation * geolocation,PassRefPtr<PositionCallback> successCallback,PassRefPtr<PositionErrorCallback> errorCallback,PassRefPtr<PositionOptions> options)46 Geolocation::GeoNotifier::GeoNotifier(Geolocation* geolocation, PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options)
47     : m_geolocation(geolocation)
48     , m_successCallback(successCallback)
49     , m_errorCallback(errorCallback)
50     , m_options(options)
51     , m_timer(this, &Geolocation::GeoNotifier::timerFired)
52     , m_fatalError(0)
53 {
54     ASSERT(m_geolocation);
55     ASSERT(m_successCallback);
56     // If no options were supplied from JS, we should have created a default set
57     // of options in JSGeolocationCustom.cpp.
58     ASSERT(m_options);
59 }
60 
setFatalError(PassRefPtr<PositionError> error)61 void Geolocation::GeoNotifier::setFatalError(PassRefPtr<PositionError> error)
62 {
63     m_fatalError = error;
64     m_timer.startOneShot(0);
65 }
66 
setCachedPosition(Geoposition * cachedPosition)67 void Geolocation::GeoNotifier::setCachedPosition(Geoposition* cachedPosition)
68 {
69     // We do not take owenership from the caller, but add our own ref count.
70     m_cachedPosition = cachedPosition;
71     m_timer.startOneShot(0);
72 }
73 
startTimerIfNeeded()74 void Geolocation::GeoNotifier::startTimerIfNeeded()
75 {
76     if (m_options->hasTimeout())
77         m_timer.startOneShot(m_options->timeout() / 1000.0);
78 }
79 
timerFired(Timer<GeoNotifier> *)80 void Geolocation::GeoNotifier::timerFired(Timer<GeoNotifier>*)
81 {
82     m_timer.stop();
83 
84     if (m_fatalError) {
85         if (m_errorCallback)
86             m_errorCallback->handleEvent(m_fatalError.get());
87         // This will cause this notifier to be deleted.
88         m_geolocation->fatalErrorOccurred(this);
89         return;
90     }
91 
92     if (m_cachedPosition) {
93         m_successCallback->handleEvent(m_cachedPosition.get());
94         m_geolocation->requestReturnedCachedPosition(this);
95         return;
96     }
97 
98     if (m_errorCallback) {
99         RefPtr<PositionError> error = PositionError::create(PositionError::TIMEOUT, "Timed out");
100         m_errorCallback->handleEvent(error.get());
101     }
102     m_geolocation->requestTimedOut(this);
103 }
104 
105 static const char* databaseName = "/CachedPosition.db";
106 
107 class CachedPositionManager {
108   public:
CachedPositionManager()109     CachedPositionManager()
110     {
111         if (s_instances++ == 0) {
112             s_cachedPosition = new RefPtr<Geoposition>;
113             *s_cachedPosition = readFromDB();
114         }
115     }
~CachedPositionManager()116     ~CachedPositionManager()
117     {
118         if (--s_instances == 0) {
119             if (*s_cachedPosition)
120                 writeToDB(s_cachedPosition->get());
121             delete s_cachedPosition;
122         }
123     }
setCachedPosition(Geoposition * cachedPosition)124     void setCachedPosition(Geoposition* cachedPosition)
125     {
126         // We do not take owenership from the caller, but add our own ref count.
127         *s_cachedPosition = cachedPosition;
128     }
cachedPosition()129     Geoposition* cachedPosition()
130     {
131         return s_cachedPosition->get();
132     }
setDatabasePath(String databasePath)133     static void setDatabasePath(String databasePath)
134     {
135         s_databaseFile = databasePath + databaseName;
136         // If we don't have have a cached position, attempt to read one from the
137         // DB at the new path.
138         if (s_instances && *s_cachedPosition == 0)
139             *s_cachedPosition = readFromDB();
140     }
141 
142   private:
readFromDB()143     static PassRefPtr<Geoposition> readFromDB()
144     {
145         SQLiteDatabase database;
146         if (!database.open(s_databaseFile))
147             return 0;
148 
149         // Create the table here, such that even if we've just created the
150         // DB, the commands below should succeed.
151         if (!database.executeCommand("CREATE TABLE IF NOT EXISTS CachedPosition ("
152                 "latitude REAL NOT NULL, "
153                 "longitude REAL NOT NULL, "
154                 "altitude REAL, "
155                 "accuracy REAL NOT NULL, "
156                 "altitudeAccuracy REAL, "
157                 "heading REAL, "
158                 "speed REAL, "
159                 "timestamp INTEGER NOT NULL)"))
160             return 0;
161 
162         SQLiteStatement statement(database, "SELECT * FROM CachedPosition");
163         if (statement.prepare() != SQLResultOk)
164             return 0;
165 
166         if (statement.step() != SQLResultRow)
167             return 0;
168 
169         bool providesAltitude = statement.getColumnValue(2).type() != SQLValue::NullValue;
170         bool providesAltitudeAccuracy = statement.getColumnValue(4).type() != SQLValue::NullValue;
171         bool providesHeading = statement.getColumnValue(5).type() != SQLValue::NullValue;
172         bool providesSpeed = statement.getColumnValue(6).type() != SQLValue::NullValue;
173         RefPtr<Coordinates> coordinates = Coordinates::create(statement.getColumnDouble(0),  // latitude
174                                                               statement.getColumnDouble(1),  // longitude
175                                                               providesAltitude, statement.getColumnDouble(2), // altitude
176                                                               statement.getColumnDouble(3),  // accuracy
177                                                               providesAltitudeAccuracy, statement.getColumnDouble(4), // altitudeAccuracy
178                                                               providesHeading, statement.getColumnDouble(5), // heading
179                                                               providesSpeed, statement.getColumnDouble(6)); // speed
180         return Geoposition::create(coordinates.release(), statement.getColumnInt64(7));  // timestamp
181     }
writeToDB(Geoposition * position)182     static void writeToDB(Geoposition* position)
183     {
184         ASSERT(position);
185 
186         SQLiteDatabase database;
187         if (!database.open(s_databaseFile))
188             return;
189 
190         SQLiteTransaction transaction(database);
191 
192         if (!database.executeCommand("DELETE FROM CachedPosition"))
193             return;
194 
195         SQLiteStatement statement(database, "INSERT INTO CachedPosition ("
196             "latitude, "
197             "longitude, "
198             "altitude, "
199             "accuracy, "
200             "altitudeAccuracy, "
201             "heading, "
202             "speed, "
203             "timestamp) "
204             "VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
205         if (statement.prepare() != SQLResultOk)
206             return;
207 
208         statement.bindDouble(1, position->coords()->latitude());
209         statement.bindDouble(2, position->coords()->longitude());
210         if (position->coords()->canProvideAltitude())
211             statement.bindDouble(3, position->coords()->altitude());
212         else
213             statement.bindNull(3);
214         statement.bindDouble(4, position->coords()->accuracy());
215         if (position->coords()->canProvideAltitudeAccuracy())
216             statement.bindDouble(5, position->coords()->altitudeAccuracy());
217         else
218             statement.bindNull(5);
219         if (position->coords()->canProvideHeading())
220             statement.bindDouble(6, position->coords()->heading());
221         else
222             statement.bindNull(6);
223         if (position->coords()->canProvideSpeed())
224             statement.bindDouble(7, position->coords()->speed());
225         else
226             statement.bindNull(7);
227         statement.bindInt64(8, position->timestamp());
228         if (!statement.executeCommand())
229             return;
230 
231         transaction.commit();
232     }
233     static int s_instances;
234     static RefPtr<Geoposition>* s_cachedPosition;
235     static String s_databaseFile;
236 };
237 
238 int CachedPositionManager::s_instances = 0;
239 RefPtr<Geoposition>* CachedPositionManager::s_cachedPosition;
240 String CachedPositionManager::s_databaseFile;
241 
242 
Geolocation(Frame * frame)243 Geolocation::Geolocation(Frame* frame)
244     : m_frame(frame)
245     , m_service(GeolocationService::create(this))
246     , m_allowGeolocation(Unknown)
247     , m_shouldClearCache(false)
248     , m_cachedPositionManager(new CachedPositionManager)
249 {
250     if (!m_frame)
251         return;
252     ASSERT(m_frame->document());
253     m_frame->document()->setUsingGeolocation(true);
254 
255     if (m_frame->domWindow())
256         m_frame->domWindow()->addEventListener(eventNames().unloadEvent, this, false);
257 }
258 
~Geolocation()259 Geolocation::~Geolocation()
260 {
261     if (m_frame && m_frame->domWindow())
262         m_frame->domWindow()->removeEventListener(eventNames().unloadEvent, this, false);
263 }
264 
disconnectFrame()265 void Geolocation::disconnectFrame()
266 {
267     m_service->stopUpdating();
268     if (m_frame && m_frame->document())
269         m_frame->document()->setUsingGeolocation(false);
270     m_frame = 0;
271 
272     delete m_cachedPositionManager;
273 }
274 
getCurrentPosition(PassRefPtr<PositionCallback> successCallback,PassRefPtr<PositionErrorCallback> errorCallback,PassRefPtr<PositionOptions> options)275 void Geolocation::getCurrentPosition(PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options)
276 {
277     RefPtr<GeoNotifier> notifier = makeRequest(successCallback, errorCallback, options);
278     ASSERT(notifier);
279 
280     m_oneShots.add(notifier);
281 }
282 
watchPosition(PassRefPtr<PositionCallback> successCallback,PassRefPtr<PositionErrorCallback> errorCallback,PassRefPtr<PositionOptions> options)283 int Geolocation::watchPosition(PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options)
284 {
285     RefPtr<GeoNotifier> notifier = makeRequest(successCallback, errorCallback, options);
286     ASSERT(notifier);
287 
288     static int sIdentifier = 0;
289     m_watchers.set(++sIdentifier, notifier);
290 
291     return sIdentifier;
292 }
293 
makeRequest(PassRefPtr<PositionCallback> successCallback,PassRefPtr<PositionErrorCallback> errorCallback,PassRefPtr<PositionOptions> options)294 PassRefPtr<Geolocation::GeoNotifier> Geolocation::makeRequest(PassRefPtr<PositionCallback> successCallback, PassRefPtr<PositionErrorCallback> errorCallback, PassRefPtr<PositionOptions> options)
295 {
296     RefPtr<GeoNotifier> notifier = GeoNotifier::create(this, successCallback, errorCallback, options);
297 
298     // Check whether permissions have already been denied. Note that if this is the case,
299     // the permission state can not change again in the lifetime of this page.
300     if (isDenied()) {
301         notifier->setFatalError(PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage));
302     } else {
303         if (haveSuitableCachedPosition(notifier->m_options.get())) {
304             ASSERT(m_cachedPositionManager->cachedPosition());
305             if (isAllowed())
306                 notifier->setCachedPosition(m_cachedPositionManager->cachedPosition());
307             else {
308                 m_requestsAwaitingCachedPosition.add(notifier);
309                 requestPermission();
310             }
311         } else {
312             if (m_service->startUpdating(notifier->m_options.get()))
313                 notifier->startTimerIfNeeded();
314             else
315                 notifier->setFatalError(PositionError::create(PositionError::UNKNOWN_ERROR, "Failed to start Geolocation service"));
316         }
317     }
318 
319     return notifier.release();
320 }
321 
fatalErrorOccurred(Geolocation::GeoNotifier * notifier)322 void Geolocation::fatalErrorOccurred(Geolocation::GeoNotifier* notifier)
323 {
324     // This request has failed fatally. Remove it from our lists.
325     m_oneShots.remove(notifier);
326     for (GeoNotifierMap::iterator iter = m_watchers.begin(); iter != m_watchers.end(); ++iter) {
327         if (iter->second == notifier) {
328             m_watchers.remove(iter);
329             break;
330         }
331     }
332 
333     if (!hasListeners())
334         m_service->stopUpdating();
335 }
336 
requestTimedOut(GeoNotifier * notifier)337 void Geolocation::requestTimedOut(GeoNotifier* notifier)
338 {
339     // If this is a one-shot request, stop it.
340     m_oneShots.remove(notifier);
341 
342     if (!hasListeners())
343         m_service->stopUpdating();
344 }
345 
requestReturnedCachedPosition(GeoNotifier * notifier)346 void Geolocation::requestReturnedCachedPosition(GeoNotifier* notifier)
347 {
348     // If this is a one-shot request, stop it.
349     if (m_oneShots.contains(notifier)) {
350         m_oneShots.remove(notifier);
351         if (!hasListeners())
352             m_service->stopUpdating();
353         return;
354     }
355 
356     // Otherwise, start the service to get updates.
357     if (m_service->startUpdating(notifier->m_options.get()))
358         notifier->startTimerIfNeeded();
359     else
360         notifier->setFatalError(PositionError::create(PositionError::UNKNOWN_ERROR, "Failed to start Geolocation service"));
361 }
362 
haveSuitableCachedPosition(PositionOptions * options)363 bool Geolocation::haveSuitableCachedPosition(PositionOptions* options)
364 {
365     if (m_cachedPositionManager->cachedPosition() == 0)
366         return false;
367     if (!options->hasMaximumAge())
368         return true;
369     if (options->maximumAge() == 0)
370         return false;
371     DOMTimeStamp currentTimeMillis = currentTime() * 1000.0;
372     return m_cachedPositionManager->cachedPosition()->timestamp() > currentTimeMillis - options->maximumAge();
373 }
374 
clearWatch(int watchId)375 void Geolocation::clearWatch(int watchId)
376 {
377     m_watchers.remove(watchId);
378 
379     if (!hasListeners())
380         m_service->stopUpdating();
381 }
382 
suspend()383 void Geolocation::suspend()
384 {
385     if (hasListeners())
386         m_service->suspend();
387 }
388 
resume()389 void Geolocation::resume()
390 {
391     if (hasListeners())
392         m_service->resume();
393 }
394 
setIsAllowed(bool allowed)395 void Geolocation::setIsAllowed(bool allowed)
396 {
397     // This may be due to either a new position from the service, or a cached
398     // position.
399     m_allowGeolocation = allowed ? Yes : No;
400 
401     if (!isAllowed()) {
402         RefPtr<WebCore::PositionError> error = PositionError::create(PositionError::PERMISSION_DENIED, permissionDeniedErrorMessage);
403         error->setIsFatal(true);
404         handleError(error.get());
405         return;
406     }
407 
408     // If the service has a last position, use it to call back for all requests.
409     // If any of the requests are waiting for permission for a cached position,
410     // the position from the service will be at least as fresh.
411     if (m_service->lastPosition())
412         makeSuccessCallbacks();
413     else {
414         GeoNotifierSet::const_iterator end = m_requestsAwaitingCachedPosition.end();
415         for (GeoNotifierSet::const_iterator iter = m_requestsAwaitingCachedPosition.begin(); iter != end; ++iter)
416             (*iter)->setCachedPosition(m_cachedPositionManager->cachedPosition());
417     }
418     m_requestsAwaitingCachedPosition.clear();
419 }
420 
sendError(Vector<RefPtr<GeoNotifier>> & notifiers,PositionError * error)421 void Geolocation::sendError(Vector<RefPtr<GeoNotifier> >& notifiers, PositionError* error)
422 {
423      Vector<RefPtr<GeoNotifier> >::const_iterator end = notifiers.end();
424      for (Vector<RefPtr<GeoNotifier> >::const_iterator it = notifiers.begin(); it != end; ++it) {
425          RefPtr<GeoNotifier> notifier = *it;
426 
427          if (notifier->m_errorCallback)
428              notifier->m_errorCallback->handleEvent(error);
429      }
430 }
431 
sendPosition(Vector<RefPtr<GeoNotifier>> & notifiers,Geoposition * position)432 void Geolocation::sendPosition(Vector<RefPtr<GeoNotifier> >& notifiers, Geoposition* position)
433 {
434     Vector<RefPtr<GeoNotifier> >::const_iterator end = notifiers.end();
435     for (Vector<RefPtr<GeoNotifier> >::const_iterator it = notifiers.begin(); it != end; ++it) {
436         RefPtr<GeoNotifier> notifier = *it;
437         ASSERT(notifier->m_successCallback);
438 
439         notifier->m_timer.stop();
440         notifier->m_successCallback->handleEvent(position);
441     }
442 }
443 
stopTimer(Vector<RefPtr<GeoNotifier>> & notifiers)444 void Geolocation::stopTimer(Vector<RefPtr<GeoNotifier> >& notifiers)
445 {
446     Vector<RefPtr<GeoNotifier> >::const_iterator end = notifiers.end();
447     for (Vector<RefPtr<GeoNotifier> >::const_iterator it = notifiers.begin(); it != end; ++it) {
448         RefPtr<GeoNotifier> notifier = *it;
449         notifier->m_timer.stop();
450     }
451 }
452 
stopTimersForOneShots()453 void Geolocation::stopTimersForOneShots()
454 {
455     Vector<RefPtr<GeoNotifier> > copy;
456     copyToVector(m_oneShots, copy);
457 
458     stopTimer(copy);
459 }
460 
stopTimersForWatchers()461 void Geolocation::stopTimersForWatchers()
462 {
463     Vector<RefPtr<GeoNotifier> > copy;
464     copyValuesToVector(m_watchers, copy);
465 
466     stopTimer(copy);
467 }
468 
stopTimers()469 void Geolocation::stopTimers()
470 {
471     stopTimersForOneShots();
472     stopTimersForWatchers();
473 }
474 
handleError(PositionError * error)475 void Geolocation::handleError(PositionError* error)
476 {
477     ASSERT(error);
478 
479     Vector<RefPtr<GeoNotifier> > oneShotsCopy;
480     copyToVector(m_oneShots, oneShotsCopy);
481 
482     Vector<RefPtr<GeoNotifier> > watchersCopy;
483     copyValuesToVector(m_watchers, watchersCopy);
484 
485     // Clear the lists before we make the callbacks, to avoid clearing notifiers
486     // added by calls to Geolocation methods from the callbacks.
487     m_oneShots.clear();
488     if (error->isFatal())
489         m_watchers.clear();
490 
491     sendError(oneShotsCopy, error);
492     sendError(watchersCopy, error);
493 
494     if (!hasListeners())
495         m_service->stopUpdating();
496 }
497 
requestPermission()498 void Geolocation::requestPermission()
499 {
500     if (m_allowGeolocation > Unknown)
501         return;
502 
503     if (!m_frame)
504         return;
505 
506     Page* page = m_frame->page();
507     if (!page)
508         return;
509 
510     m_allowGeolocation = InProgress;
511 
512     // Ask the chrome: it maintains the geolocation challenge policy itself.
513     page->chrome()->requestGeolocationPermissionForFrame(m_frame, this);
514 }
515 
geolocationServicePositionChanged(GeolocationService *)516 void Geolocation::geolocationServicePositionChanged(GeolocationService*)
517 {
518     ASSERT(m_service->lastPosition());
519 
520     m_cachedPositionManager->setCachedPosition(m_service->lastPosition());
521 
522     // Stop all currently running timers.
523     stopTimers();
524 
525     if (!isAllowed()) {
526         // requestPermission() will ask the chrome for permission. This may be
527         // implemented synchronously or asynchronously. In both cases,
528         // makeSucessCallbacks() will be called if permission is granted, so
529         // there's nothing more to do here.
530         requestPermission();
531         return;
532     }
533 
534     makeSuccessCallbacks();
535 }
536 
makeSuccessCallbacks()537 void Geolocation::makeSuccessCallbacks()
538 {
539     ASSERT(m_service->lastPosition());
540     ASSERT(isAllowed());
541 
542     Vector<RefPtr<GeoNotifier> > oneShotsCopy;
543     copyToVector(m_oneShots, oneShotsCopy);
544 
545     Vector<RefPtr<GeoNotifier> > watchersCopy;
546     copyValuesToVector(m_watchers, watchersCopy);
547 
548     // Clear the lists before we make the callbacks, to avoid clearing notifiers
549     // added by calls to Geolocation methods from the callbacks.
550     m_oneShots.clear();
551 
552     sendPosition(oneShotsCopy, m_service->lastPosition());
553     sendPosition(watchersCopy, m_service->lastPosition());
554 
555     if (!hasListeners())
556         m_service->stopUpdating();
557 }
558 
geolocationServiceErrorOccurred(GeolocationService * service)559 void Geolocation::geolocationServiceErrorOccurred(GeolocationService* service)
560 {
561     ASSERT(service->lastError());
562 
563     handleError(service->lastError());
564 }
565 
handleEvent(Event * event,bool)566 void Geolocation::handleEvent(Event* event, bool)
567 {
568   ASSERT_UNUSED(event, event->type() == eventTypes().unloadEvent);
569   // Cancel any ongoing requests on page unload. This is required to release
570   // references to JS callbacks in the page, to allow the frame to be cleaned up
571   // by WebKit.
572   m_oneShots.clear();
573   m_watchers.clear();
574 }
575 
setDatabasePath(String databasePath)576 void Geolocation::setDatabasePath(String databasePath)
577 {
578     CachedPositionManager::setDatabasePath(databasePath);
579 }
580 
581 } // namespace WebCore
582