|  | /* | 
|  | * 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/RunLoop.h> | 
|  | #import <wtf/Vector.h> | 
|  |  | 
|  | using namespace WebCore; | 
|  |  | 
|  | @interface WebGeolocationPosition (Internal) | 
|  | - (id)initWithGeolocationPosition:(GeolocationPositionData&&)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 NeverDestroyed<RetainPtr<WebGeolocationProviderIOS>> sharedGeolocationProvider; | 
|  | dispatch_once(&once, ^{ | 
|  | sharedGeolocationProvider.get() = adoptNS([[WebGeolocationProviderIOS alloc] init]); | 
|  | }); | 
|  | return sharedGeolocationProvider.get().get(); | 
|  | } | 
|  |  | 
|  | - (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) { | 
|  | RunLoop::main().dispatch([self, strongSelf = retainPtr(self)] { | 
|  | 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()) { | 
|  | RunLoop::main().dispatch([self, strongSelf = retainPtr(self)] { | 
|  | [_coreLocationProvider stop]; | 
|  | }); | 
|  | _enableHighAccuracy = NO; | 
|  | _lastPosition.clear(); | 
|  | } | 
|  | } | 
|  |  | 
|  | - (WebGeolocationPosition *)lastPosition | 
|  | { | 
|  | ASSERT(WebThreadIsLockedOrDisabled()); | 
|  | return _lastPosition.get(); | 
|  | } | 
|  |  | 
|  | - (void)setEnableHighAccuracy:(BOOL)enableHighAccuracy | 
|  | { | 
|  | ASSERT(WebThreadIsLockedOrDisabled()); | 
|  | _enableHighAccuracy = _enableHighAccuracy || enableHighAccuracy; | 
|  | RunLoop::main().dispatch([self, strongSelf = retainPtr(self)] { | 
|  | [_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); | 
|  |  | 
|  | RunLoop::main().dispatch([self, strongSelf = retainPtr(self)] { | 
|  | 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::GeolocationPositionData&&)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) |