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