blob: de7e439fb9d5efa4b6181201e21b77806914f9a4 [file] [log] [blame]
/*
* 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)