1/* 2 * Copyright (C) 2008, 2009, 2010, 2012, 2014 Apple Inc. All Rights Reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#if PLATFORM(IOS) 27 28#import "WebGeolocationProviderIOS.h" 29 30#import "WebGeolocationCoreLocationProvider.h" 31#import <WebGeolocationPosition.h> 32#import <WebCore/GeolocationPosition.h> 33#import <WebCore/WebCoreThread.h> 34#import <WebCore/WebCoreThreadRun.h> 35#import <wtf/HashSet.h> 36#import <wtf/HashMap.h> 37#import <wtf/RetainPtr.h> 38#import <wtf/Vector.h> 39 40#if PLATFORM(IOS) 41#import "WebDelegateImplementationCaching.h" 42#endif 43 44using namespace WebCore; 45 46@interface WebGeolocationPosition (Internal) 47- (id)initWithGeolocationPosition:(PassRefPtr<GeolocationPosition>)coreGeolocationPosition; 48@end 49 50// CoreLocation runs in the main thread. WebGeolocationProviderIOS lives on the WebThread. 51// _WebCoreLocationUpdateThreadingProxy forward updates from CoreLocation to WebGeolocationProviderIOS. 52@interface _WebCoreLocationUpdateThreadingProxy : NSObject<WebGeolocationCoreLocationUpdateListener> 53- (id)initWithProvider:(WebGeolocationProviderIOS*)provider; 54@end 55 56typedef HashMap<RetainPtr<WebView>, RetainPtr<id<WebGeolocationProviderInitializationListener> > > GeolocationInitializationCallbackMap; 57 58@implementation WebGeolocationProviderIOS { 59@private 60 RetainPtr<WebGeolocationCoreLocationProvider> _coreLocationProvider; 61 RetainPtr<_WebCoreLocationUpdateThreadingProxy> _coreLocationUpdateListenerProxy; 62 63 BOOL _enableHighAccuracy; 64 BOOL _isSuspended; 65 BOOL _shouldResetOnResume; 66 67 // WebViews waiting for CoreLocation to be ready. If the Application does not yet have the permission to use Geolocation 68 // we also have to wait for that to be granted. 69 GeolocationInitializationCallbackMap _webViewsWaitingForCoreLocationAuthorization; 70 71 // List of WebView needing the initial position after registerWebView:. This is needed because WebKit does not 72 // handle sending the position synchronously in response to registerWebView:, so we queue sending lastPosition behind a timer. 73 HashSet<WebView*> _pendingInitialPositionWebView; 74 75 // List of WebViews registered to WebGeolocationProvider for Geolocation update. 76 HashSet<WebView*> _registeredWebViews; 77 78 // All the views that might need a reset if the permission change externally. 79 HashSet<WebView*> _trackedWebViews; 80 81 RetainPtr<NSTimer> _sendLastPositionAsynchronouslyTimer; 82 RetainPtr<WebGeolocationPosition> _lastPosition; 83} 84 85static inline void abortSendLastPosition(WebGeolocationProviderIOS* provider) 86{ 87 provider->_pendingInitialPositionWebView.clear(); 88 [provider->_sendLastPositionAsynchronouslyTimer.get() invalidate]; 89 provider->_sendLastPositionAsynchronouslyTimer.clear(); 90} 91 92- (void)dealloc 93{ 94 abortSendLastPosition(self); 95 [super dealloc]; 96} 97 98#pragma mark - Public API of WebGeolocationProviderIOS. 99+ (WebGeolocationProviderIOS *)sharedGeolocationProvider 100{ 101 static dispatch_once_t once; 102 static WebGeolocationProviderIOS *sharedGeolocationProvider; 103 dispatch_once(&once, ^{ 104 sharedGeolocationProvider = [[WebGeolocationProviderIOS alloc] init]; 105 }); 106 return sharedGeolocationProvider; 107} 108 109- (void)suspend 110{ 111 ASSERT(WebThreadIsLockedOrDisabled()); 112 ASSERT(pthread_main_np()); 113 114 ASSERT(!_isSuspended); 115 _isSuspended = YES; 116 117 // A new position is acquired and sent to all registered views on resume. 118 _lastPosition.clear(); 119 abortSendLastPosition(self); 120 [_coreLocationProvider stop]; 121} 122 123- (void)resume 124{ 125 ASSERT(WebThreadIsLockedOrDisabled()); 126 ASSERT(pthread_main_np()); 127 128 ASSERT(_isSuspended); 129 _isSuspended = NO; 130 131 if (_shouldResetOnResume) { 132 [self resetGeolocation]; 133 _shouldResetOnResume = NO; 134 return; 135 } 136 137 if (_registeredWebViews.isEmpty() && _webViewsWaitingForCoreLocationAuthorization.isEmpty()) 138 return; 139 140 if (!_coreLocationProvider) { 141 ASSERT(!_coreLocationUpdateListenerProxy); 142 _coreLocationUpdateListenerProxy = adoptNS([[_WebCoreLocationUpdateThreadingProxy alloc] initWithProvider:self]); 143 _coreLocationProvider = adoptNS([[WebGeolocationCoreLocationProvider alloc] initWithListener:_coreLocationUpdateListenerProxy.get()]); 144 } 145 146 if (!_webViewsWaitingForCoreLocationAuthorization.isEmpty()) 147 [_coreLocationProvider requestGeolocationAuthorization]; 148 149 if (!_registeredWebViews.isEmpty()) { 150 [_coreLocationProvider setEnableHighAccuracy:_enableHighAccuracy]; 151 [_coreLocationProvider start]; 152 } 153} 154 155#pragma mark - Internal utility methods 156 157- (void)_handlePendingInitialPosition:(NSTimer*)timer 158{ 159 ASSERT_UNUSED(timer, timer == _sendLastPositionAsynchronouslyTimer); 160 161 if (_lastPosition) { 162 Vector<WebView*> webViewsCopy; 163 copyToVector(_pendingInitialPositionWebView, webViewsCopy); 164 for (size_t i = 0; i < webViewsCopy.size(); ++i) 165 [webViewsCopy[i] _geolocationDidChangePosition:_lastPosition.get()]; 166 } 167 abortSendLastPosition(self); 168} 169 170#pragma mark - Implementation of WebGeolocationProvider 171 172- (void)registerWebView:(WebView *)webView 173{ 174 ASSERT(WebThreadIsLockedOrDisabled()); 175 176 if (_registeredWebViews.contains(webView)) 177 return; 178 179 _registeredWebViews.add(webView); 180#if PLATFORM(IOS) 181 if (!CallUIDelegateReturningBoolean(YES, webView, @selector(webViewCanCheckGeolocationAuthorizationStatus:))) 182 return; 183#endif 184 185 if (!_isSuspended) { 186 dispatch_async(dispatch_get_main_queue(), ^{ 187 if (!_coreLocationProvider) { 188 ASSERT(!_coreLocationUpdateListenerProxy); 189 _coreLocationUpdateListenerProxy = adoptNS([[_WebCoreLocationUpdateThreadingProxy alloc] initWithProvider:self]); 190 _coreLocationProvider = adoptNS([[WebGeolocationCoreLocationProvider alloc] initWithListener:_coreLocationUpdateListenerProxy.get()]); 191 } 192 [_coreLocationProvider start]; 193 }); 194 } 195 196 // We send the lastPosition asynchronously because WebKit does not handle updating the position synchronously. 197 // On WebKit2, we could skip that and send the position directly from the UIProcess. 198 _pendingInitialPositionWebView.add(webView); 199 if (!_sendLastPositionAsynchronouslyTimer) 200 _sendLastPositionAsynchronouslyTimer = [NSTimer scheduledTimerWithTimeInterval:0 201 target:self 202 selector:@selector(_handlePendingInitialPosition:) 203 userInfo:nil 204 repeats:NO]; 205} 206 207- (void)unregisterWebView:(WebView *)webView 208{ 209 ASSERT(WebThreadIsLockedOrDisabled()); 210 211 if (!_registeredWebViews.contains(webView)) 212 return; 213 214 _registeredWebViews.remove(webView); 215 _pendingInitialPositionWebView.remove(webView); 216 217 if (_registeredWebViews.isEmpty()) { 218 dispatch_async(dispatch_get_main_queue(), ^{ 219 [_coreLocationProvider stop]; 220 }); 221 _enableHighAccuracy = NO; 222 _lastPosition.clear(); 223 } 224} 225 226- (WebGeolocationPosition *)lastPosition 227{ 228 ASSERT(WebThreadIsLockedOrDisabled()); 229 return _lastPosition.get(); 230} 231 232- (void)setEnableHighAccuracy:(BOOL)enableHighAccuracy 233{ 234 ASSERT(WebThreadIsLockedOrDisabled()); 235 _enableHighAccuracy = _enableHighAccuracy || enableHighAccuracy; 236 dispatch_async(dispatch_get_main_queue(), ^{ 237 [_coreLocationProvider setEnableHighAccuracy:_enableHighAccuracy]; 238 }); 239} 240 241- (void)initializeGeolocationForWebView:(WebView *)webView listener:(id<WebGeolocationProviderInitializationListener>)listener 242{ 243 ASSERT(WebThreadIsLockedOrDisabled()); 244 245#if PLATFORM(IOS) 246 if (!CallUIDelegateReturningBoolean(YES, webView, @selector(webViewCanCheckGeolocationAuthorizationStatus:))) 247 return; 248#endif 249 _webViewsWaitingForCoreLocationAuthorization.add(webView, listener); 250 _trackedWebViews.add(webView); 251 252 dispatch_async(dispatch_get_main_queue(), ^{ 253 if (!_coreLocationProvider) { 254 ASSERT(!_coreLocationUpdateListenerProxy); 255 _coreLocationUpdateListenerProxy = adoptNS([[_WebCoreLocationUpdateThreadingProxy alloc] initWithProvider:self]); 256 _coreLocationProvider = adoptNS([[WebGeolocationCoreLocationProvider alloc] initWithListener:_coreLocationUpdateListenerProxy.get()]); 257 } 258 [_coreLocationProvider requestGeolocationAuthorization]; 259 }); 260} 261 262- (void)geolocationAuthorizationGranted 263{ 264 ASSERT(WebThreadIsCurrent()); 265 266 GeolocationInitializationCallbackMap requests; 267 requests.swap(_webViewsWaitingForCoreLocationAuthorization); 268 269 for (const auto& slot : requests) 270 [slot.value initializationAllowedWebView:slot.key.get()]; 271} 272 273- (void)geolocationAuthorizationDenied 274{ 275 ASSERT(WebThreadIsCurrent()); 276 277 GeolocationInitializationCallbackMap requests; 278 requests.swap(_webViewsWaitingForCoreLocationAuthorization); 279 280 for (const auto& slot : requests) 281 [slot.value initializationDeniedWebView:slot.key.get()]; 282} 283 284- (void)stopTrackingWebView:(WebView*)webView 285{ 286 ASSERT(WebThreadIsLockedOrDisabled()); 287 _trackedWebViews.remove(webView); 288} 289 290#pragma mark - Mirror to WebGeolocationCoreLocationUpdateListener called by the proxy. 291 292- (void)positionChanged:(WebGeolocationPosition*)position 293{ 294 ASSERT(WebThreadIsCurrent()); 295 296 abortSendLastPosition(self); 297 298 _lastPosition = position; 299 Vector<WebView*> webViewsCopy; 300 copyToVector(_registeredWebViews, webViewsCopy); 301 for (size_t i = 0; i < webViewsCopy.size(); ++i) 302 [webViewsCopy.at(i) _geolocationDidChangePosition:_lastPosition.get()]; 303} 304 305- (void)errorOccurred:(NSString *)errorMessage 306{ 307 ASSERT(WebThreadIsCurrent()); 308 309 _lastPosition.clear(); 310 311 Vector<WebView*> webViewsCopy; 312 copyToVector(_registeredWebViews, webViewsCopy); 313 for (size_t i = 0; i < webViewsCopy.size(); ++i) 314 [webViewsCopy.at(i) _geolocationDidFailWithMessage:errorMessage]; 315} 316 317- (void)resetGeolocation 318{ 319 ASSERT(WebThreadIsCurrent()); 320 321 if (_isSuspended) { 322 _shouldResetOnResume = YES; 323 return; 324 } 325 // 1) Stop all ongoing Geolocation initialization and tracking. 326 _webViewsWaitingForCoreLocationAuthorization.clear(); 327 _registeredWebViews.clear(); 328 abortSendLastPosition(self); 329 330 // 2) Reset the views, each frame will register back if needed. 331 Vector<WebView*> webViewsCopy; 332 copyToVector(_trackedWebViews, webViewsCopy); 333 for (size_t i = 0; i < webViewsCopy.size(); ++i) 334 [webViewsCopy.at(i) _resetAllGeolocationPermission]; 335} 336@end 337 338#pragma mark - _WebCoreLocationUpdateThreadingProxy implementation. 339@implementation _WebCoreLocationUpdateThreadingProxy { 340 WebGeolocationProviderIOS* _provider; 341} 342 343- (id)initWithProvider:(WebGeolocationProviderIOS*)provider 344{ 345 self = [super init]; 346 if (self) 347 _provider = provider; 348 return self; 349} 350 351- (void)geolocationAuthorizationGranted 352{ 353 WebThreadRun(^{ 354 [_provider geolocationAuthorizationGranted]; 355 }); 356} 357 358- (void)geolocationAuthorizationDenied 359{ 360 WebThreadRun(^{ 361 [_provider geolocationAuthorizationDenied]; 362 }); 363} 364 365- (void)positionChanged:(WebCore::GeolocationPosition*)position 366{ 367 RetainPtr<WebGeolocationPosition> webPosition = adoptNS([[WebGeolocationPosition alloc] initWithGeolocationPosition:position]); 368 WebThreadRun(^{ 369 [_provider positionChanged:webPosition.get()]; 370 }); 371} 372 373- (void)errorOccurred:(NSString *)errorMessage 374{ 375 WebThreadRun(^{ 376 [_provider errorOccurred:errorMessage]; 377 }); 378} 379 380- (void)resetGeolocation 381{ 382 WebThreadRun(^{ 383 [_provider resetGeolocation]; 384 }); 385} 386@end 387 388#endif // PLATFORM(IOS) 389