c2ff350f74526e1256c19e4f52085cb351d24968
[WebKit-https.git] / Source / WebKitLegacy / ios / Misc / WebGeolocationProviderIOS.mm
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_FAMILY)
27
28 #import "WebGeolocationProviderIOS.h"
29
30 #import "WebDelegateImplementationCaching.h"
31 #import "WebGeolocationCoreLocationProvider.h"
32 #import <WebGeolocationPosition.h>
33 #import <WebUIDelegatePrivate.h>
34 #import <WebCore/GeolocationPosition.h>
35 #import <WebCore/WebCoreThread.h>
36 #import <WebCore/WebCoreThreadRun.h>
37 #import <wtf/HashSet.h>
38 #import <wtf/HashMap.h>
39 #import <wtf/RetainPtr.h>
40 #import <wtf/Vector.h>
41
42 using namespace WebCore;
43
44 @interface WebGeolocationPosition (Internal)
45 - (id)initWithGeolocationPosition:(GeolocationPosition&&)coreGeolocationPosition;
46 @end
47
48 // CoreLocation runs in the main thread. WebGeolocationProviderIOS lives on the WebThread.
49 // _WebCoreLocationUpdateThreadingProxy forward updates from CoreLocation to WebGeolocationProviderIOS.
50 @interface _WebCoreLocationUpdateThreadingProxy : NSObject<WebGeolocationCoreLocationUpdateListener>
51 - (id)initWithProvider:(WebGeolocationProviderIOS*)provider;
52 @end
53
54 typedef HashMap<RetainPtr<WebView>, RetainPtr<id<WebGeolocationProviderInitializationListener> > > GeolocationInitializationCallbackMap;
55
56 @implementation WebGeolocationProviderIOS {
57 @private
58     RetainPtr<WebGeolocationCoreLocationProvider> _coreLocationProvider;
59     RetainPtr<_WebCoreLocationUpdateThreadingProxy> _coreLocationUpdateListenerProxy;
60
61     BOOL _enableHighAccuracy;
62     BOOL _isSuspended;
63     BOOL _shouldResetOnResume;
64
65     // WebViews waiting for CoreLocation to be ready. If the Application does not yet have the permission to use Geolocation
66     // we also have to wait for that to be granted.
67     GeolocationInitializationCallbackMap _webViewsWaitingForCoreLocationAuthorization;
68
69     // List of WebView needing the initial position after registerWebView:. This is needed because WebKit does not
70     // handle sending the position synchronously in response to registerWebView:, so we queue sending lastPosition behind a timer.
71     HashSet<WebView*> _pendingInitialPositionWebView;
72
73     // List of WebViews registered to WebGeolocationProvider for Geolocation update.
74     HashSet<WebView*> _registeredWebViews;
75
76     // All the views that might need a reset if the permission change externally.
77     HashSet<WebView*> _trackedWebViews;
78
79     RetainPtr<NSTimer> _sendLastPositionAsynchronouslyTimer;
80     RetainPtr<WebGeolocationPosition> _lastPosition;
81 }
82
83 static inline void abortSendLastPosition(WebGeolocationProviderIOS* provider)
84 {
85     provider->_pendingInitialPositionWebView.clear();
86     [provider->_sendLastPositionAsynchronouslyTimer.get() invalidate];
87     provider->_sendLastPositionAsynchronouslyTimer.clear();
88 }
89
90 - (void)dealloc
91 {
92     abortSendLastPosition(self);
93     [super dealloc];
94 }
95
96 #pragma mark - Public API of WebGeolocationProviderIOS.
97 + (WebGeolocationProviderIOS *)sharedGeolocationProvider
98 {
99     static dispatch_once_t once;
100     static WebGeolocationProviderIOS *sharedGeolocationProvider;
101     dispatch_once(&once, ^{
102         sharedGeolocationProvider = [[WebGeolocationProviderIOS alloc] init];
103     });
104     return sharedGeolocationProvider;
105 }
106
107 - (void)suspend
108 {
109     ASSERT(WebThreadIsLockedOrDisabled());
110     ASSERT(pthread_main_np());
111
112     ASSERT(!_isSuspended);
113     _isSuspended = YES;
114
115     // A new position is acquired and sent to all registered views on resume.
116     _lastPosition.clear();
117     abortSendLastPosition(self);
118     [_coreLocationProvider stop];
119 }
120
121 - (void)resume
122 {
123     ASSERT(WebThreadIsLockedOrDisabled());
124     ASSERT(pthread_main_np());
125
126     ASSERT(_isSuspended);
127     _isSuspended = NO;
128
129     if (_shouldResetOnResume) {
130         [self resetGeolocation];
131         _shouldResetOnResume = NO;
132         return;
133     }
134
135     if (_registeredWebViews.isEmpty() && _webViewsWaitingForCoreLocationAuthorization.isEmpty())
136         return;
137
138     if (!_coreLocationProvider) {
139         ASSERT(!_coreLocationUpdateListenerProxy);
140         _coreLocationUpdateListenerProxy = adoptNS([[_WebCoreLocationUpdateThreadingProxy alloc] initWithProvider:self]);
141         _coreLocationProvider = adoptNS([[WebGeolocationCoreLocationProvider alloc] initWithListener:_coreLocationUpdateListenerProxy.get()]);
142     }
143
144     if (!_webViewsWaitingForCoreLocationAuthorization.isEmpty())
145         [_coreLocationProvider requestGeolocationAuthorization];
146
147     if (!_registeredWebViews.isEmpty()) {
148         [_coreLocationProvider setEnableHighAccuracy:_enableHighAccuracy];
149         [_coreLocationProvider start];
150     }
151 }
152
153 #pragma mark - Internal utility methods
154
155 - (void)_handlePendingInitialPosition:(NSTimer*)timer
156 {
157     ASSERT_UNUSED(timer, timer == _sendLastPositionAsynchronouslyTimer);
158     ASSERT(WebThreadIsCurrent());
159
160     if (_lastPosition) {
161         for (auto& webView : copyToVector(_pendingInitialPositionWebView))
162             [webView _geolocationDidChangePosition:_lastPosition.get()];
163     }
164     abortSendLastPosition(self);
165 }
166
167 #pragma mark - Implementation of WebGeolocationProvider
168
169 - (void)registerWebView:(WebView *)webView
170 {
171     ASSERT(WebThreadIsLockedOrDisabled());
172
173     if (_registeredWebViews.contains(webView))
174         return;
175
176     _registeredWebViews.add(webView);
177     if (!CallUIDelegateReturningBoolean(YES, webView, @selector(webViewCanCheckGeolocationAuthorizationStatus:)))
178         return;
179
180     if (!_isSuspended) {
181         dispatch_async(dispatch_get_main_queue(), ^{
182             if (!_coreLocationProvider) {
183                 ASSERT(!_coreLocationUpdateListenerProxy);
184                 _coreLocationUpdateListenerProxy = adoptNS([[_WebCoreLocationUpdateThreadingProxy alloc] initWithProvider:self]);
185                 _coreLocationProvider = adoptNS([[WebGeolocationCoreLocationProvider alloc] initWithListener:_coreLocationUpdateListenerProxy.get()]);
186             }
187             [_coreLocationProvider start];
188         });
189     }
190
191     // We send the lastPosition asynchronously because WebKit does not handle updating the position synchronously.
192     // On WebKit2, we could skip that and send the position directly from the UIProcess.
193     _pendingInitialPositionWebView.add(webView);
194     if (!_sendLastPositionAsynchronouslyTimer) {
195         _sendLastPositionAsynchronouslyTimer = [NSTimer timerWithTimeInterval:0 target:self selector:@selector(_handlePendingInitialPosition:) userInfo:nil repeats:NO];
196         [WebThreadNSRunLoop() addTimer:_sendLastPositionAsynchronouslyTimer.get() forMode:NSDefaultRunLoopMode];
197     }
198 }
199
200 - (void)unregisterWebView:(WebView *)webView
201 {
202     ASSERT(WebThreadIsLockedOrDisabled());
203
204     if (!_registeredWebViews.contains(webView))
205         return;
206
207     _registeredWebViews.remove(webView);
208     _pendingInitialPositionWebView.remove(webView);
209
210     if (_registeredWebViews.isEmpty()) {
211         dispatch_async(dispatch_get_main_queue(), ^{
212             [_coreLocationProvider stop];
213         });
214         _enableHighAccuracy = NO;
215         _lastPosition.clear();
216     }
217 }
218
219 - (WebGeolocationPosition *)lastPosition
220 {
221     ASSERT(WebThreadIsLockedOrDisabled());
222     return _lastPosition.get();
223 }
224
225 - (void)setEnableHighAccuracy:(BOOL)enableHighAccuracy
226 {
227     ASSERT(WebThreadIsLockedOrDisabled());
228     _enableHighAccuracy = _enableHighAccuracy || enableHighAccuracy;
229     dispatch_async(dispatch_get_main_queue(), ^{
230         [_coreLocationProvider setEnableHighAccuracy:_enableHighAccuracy];
231     });
232 }
233
234 - (void)initializeGeolocationForWebView:(WebView *)webView listener:(id<WebGeolocationProviderInitializationListener>)listener
235 {
236     ASSERT(WebThreadIsLockedOrDisabled());
237
238     if (!CallUIDelegateReturningBoolean(YES, webView, @selector(webViewCanCheckGeolocationAuthorizationStatus:)))
239         return;
240
241     _webViewsWaitingForCoreLocationAuthorization.add(webView, listener);
242     _trackedWebViews.add(webView);
243
244     dispatch_async(dispatch_get_main_queue(), ^{
245         if (!_coreLocationProvider) {
246             ASSERT(!_coreLocationUpdateListenerProxy);
247             _coreLocationUpdateListenerProxy = adoptNS([[_WebCoreLocationUpdateThreadingProxy alloc] initWithProvider:self]);
248             _coreLocationProvider = adoptNS([[WebGeolocationCoreLocationProvider alloc] initWithListener:_coreLocationUpdateListenerProxy.get()]);
249         }
250         [_coreLocationProvider requestGeolocationAuthorization];
251     });
252 }
253
254 - (void)geolocationAuthorizationGranted
255 {
256     ASSERT(WebThreadIsCurrent());
257
258     GeolocationInitializationCallbackMap requests;
259     requests.swap(_webViewsWaitingForCoreLocationAuthorization);
260
261     for (const auto& slot : requests)
262         [slot.value initializationAllowedWebView:slot.key.get()];
263 }
264
265 - (void)geolocationAuthorizationDenied
266 {
267     ASSERT(WebThreadIsCurrent());
268
269     GeolocationInitializationCallbackMap requests;
270     requests.swap(_webViewsWaitingForCoreLocationAuthorization);
271
272     for (const auto& slot : requests)
273         [slot.value initializationDeniedWebView:slot.key.get()];
274 }
275
276 - (void)stopTrackingWebView:(WebView*)webView
277 {
278     ASSERT(WebThreadIsLockedOrDisabled());
279     _trackedWebViews.remove(webView);
280 }
281
282 #pragma mark - Mirror to WebGeolocationCoreLocationUpdateListener called by the proxy.
283
284 - (void)positionChanged:(WebGeolocationPosition*)position
285 {
286     ASSERT(WebThreadIsCurrent());
287
288     abortSendLastPosition(self);
289
290     _lastPosition = position;
291     for (auto& webView : copyToVector(_registeredWebViews))
292         [webView _geolocationDidChangePosition:_lastPosition.get()];
293 }
294
295 - (void)errorOccurred:(NSString *)errorMessage
296 {
297     ASSERT(WebThreadIsCurrent());
298
299     _lastPosition.clear();
300
301     for (auto& webView : copyToVector(_registeredWebViews))
302         [webView _geolocationDidFailWithMessage:errorMessage];
303 }
304
305 - (void)resetGeolocation
306 {
307     ASSERT(WebThreadIsCurrent());
308
309     if (_isSuspended) {
310         _shouldResetOnResume = YES;
311         return;
312     }
313     // 1) Stop all ongoing Geolocation initialization and tracking.
314     _webViewsWaitingForCoreLocationAuthorization.clear();
315     _registeredWebViews.clear();
316     abortSendLastPosition(self);
317
318     // 2) Reset the views, each frame will register back if needed.
319     for (auto& webView : copyToVector(_trackedWebViews))
320         [webView _resetAllGeolocationPermission];
321 }
322 @end
323
324 #pragma mark - _WebCoreLocationUpdateThreadingProxy implementation.
325 @implementation _WebCoreLocationUpdateThreadingProxy {
326     WebGeolocationProviderIOS* _provider;
327 }
328
329 - (id)initWithProvider:(WebGeolocationProviderIOS*)provider
330 {
331     self = [super init];
332     if (self)
333         _provider = provider;
334     return self;
335 }
336
337 - (void)geolocationAuthorizationGranted
338 {
339     WebThreadRun(^{
340         [_provider geolocationAuthorizationGranted];
341     });
342 }
343
344 - (void)geolocationAuthorizationDenied
345 {
346     WebThreadRun(^{
347         [_provider geolocationAuthorizationDenied];
348     });
349 }
350
351 - (void)positionChanged:(WebCore::GeolocationPosition&&)position
352 {
353     RetainPtr<WebGeolocationPosition> webPosition = adoptNS([[WebGeolocationPosition alloc] initWithGeolocationPosition:WTFMove(position)]);
354     WebThreadRun(^{
355         [_provider positionChanged:webPosition.get()];
356     });
357 }
358
359 - (void)errorOccurred:(NSString *)errorMessage
360 {
361     WebThreadRun(^{
362         [_provider errorOccurred:errorMessage];
363     });
364 }
365
366 - (void)resetGeolocation
367 {
368     WebThreadRun(^{
369         [_provider resetGeolocation];
370     });
371 }
372 @end
373
374 #endif // PLATFORM(IOS_FAMILY)