| /* | 
 |  * Copyright (C) 2008, 2009, 2010, 2012, 2014 Apple Inc. All Rights Reserved. | 
 |  * | 
 |  * Redistribution and use in source and binary forms, with or without | 
 |  * modification, are permitted provided that the following conditions | 
 |  * are met: | 
 |  * 1. Redistributions of source code must retain the above copyright | 
 |  *    notice, this list of conditions and the following disclaimer. | 
 |  * 2. Redistributions in binary form must reproduce the above copyright | 
 |  *    notice, this list of conditions and the following disclaimer in the | 
 |  *    documentation and/or other materials provided with the distribution. | 
 |  * | 
 |  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY | 
 |  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
 |  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | 
 |  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR | 
 |  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | 
 |  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | 
 |  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | 
 |  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | 
 |  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
 |  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
 |  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
 |  */ | 
 |  | 
 | #if PLATFORM(IOS_FAMILY) | 
 |  | 
 | #import "WebGeolocationProviderIOS.h" | 
 |  | 
 | #import "WebDelegateImplementationCaching.h" | 
 | #import "WebGeolocationCoreLocationProvider.h" | 
 | #import <WebGeolocationPosition.h> | 
 | #import <WebUIDelegatePrivate.h> | 
 | #import <WebCore/GeolocationPosition.h> | 
 | #import <WebCore/WebCoreThread.h> | 
 | #import <WebCore/WebCoreThreadRun.h> | 
 | #import <wtf/HashSet.h> | 
 | #import <wtf/HashMap.h> | 
 | #import <wtf/RetainPtr.h> | 
 | #import <wtf/Vector.h> | 
 |  | 
 | using namespace WebCore; | 
 |  | 
 | @interface WebGeolocationPosition (Internal) | 
 | - (id)initWithGeolocationPosition:(GeolocationPosition&&)coreGeolocationPosition; | 
 | @end | 
 |  | 
 | // CoreLocation runs in the main thread. WebGeolocationProviderIOS lives on the WebThread. | 
 | // _WebCoreLocationUpdateThreadingProxy forward updates from CoreLocation to WebGeolocationProviderIOS. | 
 | @interface _WebCoreLocationUpdateThreadingProxy : NSObject<WebGeolocationCoreLocationUpdateListener> | 
 | - (id)initWithProvider:(WebGeolocationProviderIOS*)provider; | 
 | @end | 
 |  | 
 | typedef HashMap<RetainPtr<WebView>, RetainPtr<id<WebGeolocationProviderInitializationListener> > > GeolocationInitializationCallbackMap; | 
 |  | 
 | @implementation WebGeolocationProviderIOS { | 
 | @private | 
 |     RetainPtr<WebGeolocationCoreLocationProvider> _coreLocationProvider; | 
 |     RetainPtr<_WebCoreLocationUpdateThreadingProxy> _coreLocationUpdateListenerProxy; | 
 |  | 
 |     BOOL _enableHighAccuracy; | 
 |     BOOL _isSuspended; | 
 |     BOOL _shouldResetOnResume; | 
 |  | 
 |     // WebViews waiting for CoreLocation to be ready. If the Application does not yet have the permission to use Geolocation | 
 |     // we also have to wait for that to be granted. | 
 |     GeolocationInitializationCallbackMap _webViewsWaitingForCoreLocationAuthorization; | 
 |  | 
 |     // List of WebView needing the initial position after registerWebView:. This is needed because WebKit does not | 
 |     // handle sending the position synchronously in response to registerWebView:, so we queue sending lastPosition behind a timer. | 
 |     HashSet<WebView*> _pendingInitialPositionWebView; | 
 |  | 
 |     // List of WebViews registered to WebGeolocationProvider for Geolocation update. | 
 |     HashSet<WebView*> _registeredWebViews; | 
 |  | 
 |     // All the views that might need a reset if the permission change externally. | 
 |     HashSet<WebView*> _trackedWebViews; | 
 |  | 
 |     RetainPtr<NSTimer> _sendLastPositionAsynchronouslyTimer; | 
 |     RetainPtr<WebGeolocationPosition> _lastPosition; | 
 | } | 
 |  | 
 | static inline void abortSendLastPosition(WebGeolocationProviderIOS* provider) | 
 | { | 
 |     provider->_pendingInitialPositionWebView.clear(); | 
 |     [provider->_sendLastPositionAsynchronouslyTimer.get() invalidate]; | 
 |     provider->_sendLastPositionAsynchronouslyTimer.clear(); | 
 | } | 
 |  | 
 | - (void)dealloc | 
 | { | 
 |     abortSendLastPosition(self); | 
 |     [super dealloc]; | 
 | } | 
 |  | 
 | #pragma mark - Public API of WebGeolocationProviderIOS. | 
 | + (WebGeolocationProviderIOS *)sharedGeolocationProvider | 
 | { | 
 |     static dispatch_once_t once; | 
 |     static WebGeolocationProviderIOS *sharedGeolocationProvider; | 
 |     dispatch_once(&once, ^{ | 
 |         sharedGeolocationProvider = [[WebGeolocationProviderIOS alloc] init]; | 
 |     }); | 
 |     return sharedGeolocationProvider; | 
 | } | 
 |  | 
 | - (void)suspend | 
 | { | 
 |     ASSERT(WebThreadIsLockedOrDisabled()); | 
 |     ASSERT(pthread_main_np()); | 
 |  | 
 |     ASSERT(!_isSuspended); | 
 |     _isSuspended = YES; | 
 |  | 
 |     // A new position is acquired and sent to all registered views on resume. | 
 |     _lastPosition.clear(); | 
 |     abortSendLastPosition(self); | 
 |     [_coreLocationProvider stop]; | 
 | } | 
 |  | 
 | - (void)resume | 
 | { | 
 |     ASSERT(WebThreadIsLockedOrDisabled()); | 
 |     ASSERT(pthread_main_np()); | 
 |  | 
 |     ASSERT(_isSuspended); | 
 |     _isSuspended = NO; | 
 |  | 
 |     if (_shouldResetOnResume) { | 
 |         [self resetGeolocation]; | 
 |         _shouldResetOnResume = NO; | 
 |         return; | 
 |     } | 
 |  | 
 |     if (_registeredWebViews.isEmpty() && _webViewsWaitingForCoreLocationAuthorization.isEmpty()) | 
 |         return; | 
 |  | 
 |     if (!_coreLocationProvider) { | 
 |         ASSERT(!_coreLocationUpdateListenerProxy); | 
 |         _coreLocationUpdateListenerProxy = adoptNS([[_WebCoreLocationUpdateThreadingProxy alloc] initWithProvider:self]); | 
 |         _coreLocationProvider = adoptNS([[WebGeolocationCoreLocationProvider alloc] initWithListener:_coreLocationUpdateListenerProxy.get()]); | 
 |     } | 
 |  | 
 |     if (!_webViewsWaitingForCoreLocationAuthorization.isEmpty()) | 
 |         [_coreLocationProvider requestGeolocationAuthorization]; | 
 |  | 
 |     if (!_registeredWebViews.isEmpty()) { | 
 |         [_coreLocationProvider setEnableHighAccuracy:_enableHighAccuracy]; | 
 |         [_coreLocationProvider start]; | 
 |     } | 
 | } | 
 |  | 
 | #pragma mark - Internal utility methods | 
 |  | 
 | - (void)_handlePendingInitialPosition:(NSTimer*)timer | 
 | { | 
 |     ASSERT_UNUSED(timer, timer == _sendLastPositionAsynchronouslyTimer); | 
 |     ASSERT(WebThreadIsCurrent()); | 
 |  | 
 |     if (_lastPosition) { | 
 |         for (auto& webView : copyToVector(_pendingInitialPositionWebView)) | 
 |             [webView _geolocationDidChangePosition:_lastPosition.get()]; | 
 |     } | 
 |     abortSendLastPosition(self); | 
 | } | 
 |  | 
 | #pragma mark - Implementation of WebGeolocationProvider | 
 |  | 
 | - (void)registerWebView:(WebView *)webView | 
 | { | 
 |     ASSERT(WebThreadIsLockedOrDisabled()); | 
 |  | 
 |     if (_registeredWebViews.contains(webView)) | 
 |         return; | 
 |  | 
 |     _registeredWebViews.add(webView); | 
 |     if (!CallUIDelegateReturningBoolean(YES, webView, @selector(webViewCanCheckGeolocationAuthorizationStatus:))) | 
 |         return; | 
 |  | 
 |     if (!_isSuspended) { | 
 |         dispatch_async(dispatch_get_main_queue(), ^{ | 
 |             if (!_coreLocationProvider) { | 
 |                 ASSERT(!_coreLocationUpdateListenerProxy); | 
 |                 _coreLocationUpdateListenerProxy = adoptNS([[_WebCoreLocationUpdateThreadingProxy alloc] initWithProvider:self]); | 
 |                 _coreLocationProvider = adoptNS([[WebGeolocationCoreLocationProvider alloc] initWithListener:_coreLocationUpdateListenerProxy.get()]); | 
 |             } | 
 |             [_coreLocationProvider start]; | 
 |         }); | 
 |     } | 
 |  | 
 |     // We send the lastPosition asynchronously because WebKit does not handle updating the position synchronously. | 
 |     // On WebKit2, we could skip that and send the position directly from the UIProcess. | 
 |     _pendingInitialPositionWebView.add(webView); | 
 |     if (!_sendLastPositionAsynchronouslyTimer) { | 
 |         _sendLastPositionAsynchronouslyTimer = [NSTimer timerWithTimeInterval:0 target:self selector:@selector(_handlePendingInitialPosition:) userInfo:nil repeats:NO]; | 
 |         [WebThreadNSRunLoop() addTimer:_sendLastPositionAsynchronouslyTimer.get() forMode:NSDefaultRunLoopMode]; | 
 |     } | 
 | } | 
 |  | 
 | - (void)unregisterWebView:(WebView *)webView | 
 | { | 
 |     ASSERT(WebThreadIsLockedOrDisabled()); | 
 |  | 
 |     if (!_registeredWebViews.contains(webView)) | 
 |         return; | 
 |  | 
 |     _registeredWebViews.remove(webView); | 
 |     _pendingInitialPositionWebView.remove(webView); | 
 |  | 
 |     if (_registeredWebViews.isEmpty()) { | 
 |         dispatch_async(dispatch_get_main_queue(), ^{ | 
 |             [_coreLocationProvider stop]; | 
 |         }); | 
 |         _enableHighAccuracy = NO; | 
 |         _lastPosition.clear(); | 
 |     } | 
 | } | 
 |  | 
 | - (WebGeolocationPosition *)lastPosition | 
 | { | 
 |     ASSERT(WebThreadIsLockedOrDisabled()); | 
 |     return _lastPosition.get(); | 
 | } | 
 |  | 
 | - (void)setEnableHighAccuracy:(BOOL)enableHighAccuracy | 
 | { | 
 |     ASSERT(WebThreadIsLockedOrDisabled()); | 
 |     _enableHighAccuracy = _enableHighAccuracy || enableHighAccuracy; | 
 |     dispatch_async(dispatch_get_main_queue(), ^{ | 
 |         [_coreLocationProvider setEnableHighAccuracy:_enableHighAccuracy]; | 
 |     }); | 
 | } | 
 |  | 
 | - (void)initializeGeolocationForWebView:(WebView *)webView listener:(id<WebGeolocationProviderInitializationListener>)listener | 
 | { | 
 |     ASSERT(WebThreadIsLockedOrDisabled()); | 
 |  | 
 |     if (!CallUIDelegateReturningBoolean(YES, webView, @selector(webViewCanCheckGeolocationAuthorizationStatus:))) | 
 |         return; | 
 |  | 
 |     _webViewsWaitingForCoreLocationAuthorization.add(webView, listener); | 
 |     _trackedWebViews.add(webView); | 
 |  | 
 |     dispatch_async(dispatch_get_main_queue(), ^{ | 
 |         if (!_coreLocationProvider) { | 
 |             ASSERT(!_coreLocationUpdateListenerProxy); | 
 |             _coreLocationUpdateListenerProxy = adoptNS([[_WebCoreLocationUpdateThreadingProxy alloc] initWithProvider:self]); | 
 |             _coreLocationProvider = adoptNS([[WebGeolocationCoreLocationProvider alloc] initWithListener:_coreLocationUpdateListenerProxy.get()]); | 
 |         } | 
 |         [_coreLocationProvider requestGeolocationAuthorization]; | 
 |     }); | 
 | } | 
 |  | 
 | - (void)geolocationAuthorizationGranted | 
 | { | 
 |     ASSERT(WebThreadIsCurrent()); | 
 |  | 
 |     GeolocationInitializationCallbackMap requests; | 
 |     requests.swap(_webViewsWaitingForCoreLocationAuthorization); | 
 |  | 
 |     for (const auto& slot : requests) | 
 |         [slot.value initializationAllowedWebView:slot.key.get()]; | 
 | } | 
 |  | 
 | - (void)geolocationAuthorizationDenied | 
 | { | 
 |     ASSERT(WebThreadIsCurrent()); | 
 |  | 
 |     GeolocationInitializationCallbackMap requests; | 
 |     requests.swap(_webViewsWaitingForCoreLocationAuthorization); | 
 |  | 
 |     for (const auto& slot : requests) | 
 |         [slot.value initializationDeniedWebView:slot.key.get()]; | 
 | } | 
 |  | 
 | - (void)stopTrackingWebView:(WebView*)webView | 
 | { | 
 |     ASSERT(WebThreadIsLockedOrDisabled()); | 
 |     _trackedWebViews.remove(webView); | 
 | } | 
 |  | 
 | #pragma mark - Mirror to WebGeolocationCoreLocationUpdateListener called by the proxy. | 
 |  | 
 | - (void)positionChanged:(WebGeolocationPosition*)position | 
 | { | 
 |     ASSERT(WebThreadIsCurrent()); | 
 |  | 
 |     abortSendLastPosition(self); | 
 |  | 
 |     _lastPosition = position; | 
 |     for (auto& webView : copyToVector(_registeredWebViews)) | 
 |         [webView _geolocationDidChangePosition:_lastPosition.get()]; | 
 | } | 
 |  | 
 | - (void)errorOccurred:(NSString *)errorMessage | 
 | { | 
 |     ASSERT(WebThreadIsCurrent()); | 
 |  | 
 |     _lastPosition.clear(); | 
 |  | 
 |     for (auto& webView : copyToVector(_registeredWebViews)) | 
 |         [webView _geolocationDidFailWithMessage:errorMessage]; | 
 | } | 
 |  | 
 | - (void)resetGeolocation | 
 | { | 
 |     ASSERT(WebThreadIsCurrent()); | 
 |  | 
 |     if (_isSuspended) { | 
 |         _shouldResetOnResume = YES; | 
 |         return; | 
 |     } | 
 |     // 1) Stop all ongoing Geolocation initialization and tracking. | 
 |     _webViewsWaitingForCoreLocationAuthorization.clear(); | 
 |     _registeredWebViews.clear(); | 
 |     abortSendLastPosition(self); | 
 |  | 
 |     // 2) Reset the views, each frame will register back if needed. | 
 |     for (auto& webView : copyToVector(_trackedWebViews)) | 
 |         [webView _resetAllGeolocationPermission]; | 
 | } | 
 | @end | 
 |  | 
 | #pragma mark - _WebCoreLocationUpdateThreadingProxy implementation. | 
 | @implementation _WebCoreLocationUpdateThreadingProxy { | 
 |     WebGeolocationProviderIOS* _provider; | 
 | } | 
 |  | 
 | - (id)initWithProvider:(WebGeolocationProviderIOS*)provider | 
 | { | 
 |     self = [super init]; | 
 |     if (self) | 
 |         _provider = provider; | 
 |     return self; | 
 | } | 
 |  | 
 | - (void)geolocationAuthorizationGranted | 
 | { | 
 |     WebThreadRun(^{ | 
 |         [_provider geolocationAuthorizationGranted]; | 
 |     }); | 
 | } | 
 |  | 
 | - (void)geolocationAuthorizationDenied | 
 | { | 
 |     WebThreadRun(^{ | 
 |         [_provider geolocationAuthorizationDenied]; | 
 |     }); | 
 | } | 
 |  | 
 | - (void)positionChanged:(WebCore::GeolocationPosition&&)position | 
 | { | 
 |     RetainPtr<WebGeolocationPosition> webPosition = adoptNS([[WebGeolocationPosition alloc] initWithGeolocationPosition:WTFMove(position)]); | 
 |     WebThreadRun(^{ | 
 |         [_provider positionChanged:webPosition.get()]; | 
 |     }); | 
 | } | 
 |  | 
 | - (void)errorOccurred:(NSString *)errorMessage | 
 | { | 
 |     WebThreadRun(^{ | 
 |         [_provider errorOccurred:errorMessage]; | 
 |     }); | 
 | } | 
 |  | 
 | - (void)resetGeolocation | 
 | { | 
 |     WebThreadRun(^{ | 
 |         [_provider resetGeolocation]; | 
 |     }); | 
 | } | 
 | @end | 
 |  | 
 | #endif // PLATFORM(IOS_FAMILY) |