1/*
2 * Copyright (C) 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. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "WKGeolocationProviderIOS.h"
28
29#if PLATFORM(IOS)
30
31#import "GeolocationPermissionRequestProxy.h"
32#import "WebContext.h"
33#import "WebGeolocationManagerProxy.h"
34#import "WebSecurityOrigin.h"
35#import <WebGeolocationPosition.h>
36#import <WebCore/GeolocationPosition.h>
37#import <WebKit/WKGeolocationPermissionRequest.h>
38#import <wtf/Assertions.h>
39#import <wtf/PassRefPtr.h>
40#import <wtf/RefPtr.h>
41#import <wtf/RetainPtr.h>
42#import <wtf/HashSet.h>
43
44// FIXME: Remove use of WebKit1 from WebKit2
45#import <WebKit/WebGeolocationCoreLocationProvider.h>
46#import <WebKit/WebAllowDenyPolicyListener.h>
47
48using namespace WebKit;
49
50#pragma clang diagnostic push
51#pragma clang diagnostic ignored "-Wdeprecated-declarations"
52
53@interface WKGeolocationProviderIOS (WebGeolocationCoreLocationUpdateListener) <WebGeolocationCoreLocationUpdateListener>
54@end
55
56@interface WKWebAllowDenyPolicyListener : NSObject<WebAllowDenyPolicyListener>
57- (id)initWithPermissionRequestProxy:(PassRefPtr<GeolocationPermissionRequestProxy>)permissionRequestProxy;
58- (void)denyOnlyThisRequest NO_RETURN_DUE_TO_ASSERT;
59@end
60
61namespace WebKit {
62void decidePolicyForGeolocationRequestFromOrigin(WebCore::SecurityOrigin*, const String& urlString, id<WebAllowDenyPolicyListener>, UIWindow*);
63};
64
65struct GeolocationRequestData {
66    RefPtr<WebCore::SecurityOrigin> origin;
67    RefPtr<WebFrameProxy> frame;
68    RefPtr<GeolocationPermissionRequestProxy> permissionRequest;
69    RetainPtr<UIWindow> window;
70};
71
72@implementation WKGeolocationProviderIOS {
73    RefPtr<WebGeolocationManagerProxy> _geolocationManager;
74    RetainPtr<WebGeolocationCoreLocationProvider> _coreLocationProvider;
75    BOOL _isWebCoreGeolocationActive;
76    RefPtr<WebGeolocationPosition> _lastActivePosition;
77    Vector<GeolocationRequestData> _requestsWaitingForCoreLocationAuthorization;
78}
79
80#pragma mark - WKGeolocationProvider callbacks implementation.
81
82static void startUpdatingCallback(WKGeolocationManagerRef geolocationManager, const void* clientInfo)
83{
84    WKGeolocationProviderIOS *geolocationProvider = reinterpret_cast<WKGeolocationProviderIOS*>(const_cast<void*>(clientInfo));
85    ASSERT([geolocationProvider isKindOfClass:[WKGeolocationProviderIOS class]]);
86    [geolocationProvider _startUpdating];
87}
88
89static void stopUpdatingCallback(WKGeolocationManagerRef geolocationManager, const void* clientInfo)
90{
91    WKGeolocationProviderIOS *geolocationProvider = reinterpret_cast<WKGeolocationProviderIOS*>(const_cast<void*>(clientInfo));
92    ASSERT([geolocationProvider isKindOfClass:[WKGeolocationProviderIOS class]]);
93    [geolocationProvider _stopUpdating];
94}
95
96static void setEnableHighAccuracy(WKGeolocationManagerRef geolocationManager, bool enable, const void* clientInfo)
97{
98    WKGeolocationProviderIOS *geolocationProvider = reinterpret_cast<WKGeolocationProviderIOS*>(const_cast<void*>(clientInfo));
99    ASSERT([geolocationProvider isKindOfClass:[WKGeolocationProviderIOS class]]);
100    [geolocationProvider _setEnableHighAccuracy:enable];
101}
102
103-(void)_startUpdating
104{
105    _isWebCoreGeolocationActive = YES;
106    [_coreLocationProvider start];
107
108    // If we have the last position, it is from the initialization or warm up. It is the last known
109    // good position so we can return it directly.
110    if (_lastActivePosition)
111        _geolocationManager->providerDidChangePosition(_lastActivePosition.get());
112}
113
114-(void)_stopUpdating
115{
116    _isWebCoreGeolocationActive = NO;
117    [_coreLocationProvider stop];
118    _lastActivePosition.clear();
119}
120
121-(void)_setEnableHighAccuracy:(BOOL)enableHighAccuracy
122{
123    [_coreLocationProvider setEnableHighAccuracy:enableHighAccuracy];
124}
125
126#pragma mark - Public API implementation.
127
128-(id)init
129{
130    ASSERT_NOT_REACHED();
131    [self release];
132    return nil;
133}
134
135-(id)initWithContext:(WebContext*)context
136{
137    self = [super init];
138    if (!self)
139        return nil;
140    _geolocationManager = context->supplement<WebGeolocationManagerProxy>();
141    WKGeolocationProvider providerCallback = {
142        kWKGeolocationProviderCurrentVersion,
143        self,
144        startUpdatingCallback,
145        stopUpdatingCallback,
146        setEnableHighAccuracy
147    };
148    _geolocationManager->initializeProvider(reinterpret_cast<WKGeolocationProviderBase*>(&providerCallback));
149    _coreLocationProvider = adoptNS([[WebGeolocationCoreLocationProvider alloc] initWithListener:self]);
150    return self;
151}
152
153-(void)decidePolicyForGeolocationRequestFromOrigin:(WKSecurityOriginRef)origin frame:(WKFrameRef)frame request:(WKGeolocationPermissionRequestRef)permissionRequest window:(UIWindow*)window
154{
155    // Step 1: ask the user if the app can use Geolocation.
156    GeolocationRequestData geolocationRequestData;
157    geolocationRequestData.origin = const_cast<WebCore::SecurityOrigin*>(&toImpl(origin)->securityOrigin());
158    geolocationRequestData.frame = toImpl(frame);
159    geolocationRequestData.permissionRequest = toImpl(permissionRequest);
160    geolocationRequestData.window = window;
161    _requestsWaitingForCoreLocationAuthorization.append(geolocationRequestData);
162    [_coreLocationProvider requestGeolocationAuthorization];
163}
164@end
165
166#pragma mark - WebGeolocationCoreLocationUpdateListener implementation.
167
168@implementation WKGeolocationProviderIOS (WebGeolocationCoreLocationUpdateListener)
169
170- (void)geolocationAuthorizationGranted
171{
172    // Step 2: ask the user if the this particular page can use gelocation.
173    Vector<GeolocationRequestData> requests = WTF::move(_requestsWaitingForCoreLocationAuthorization);
174    for (const auto& request : requests) {
175        RetainPtr<WKWebAllowDenyPolicyListener> policyListener = adoptNS([[WKWebAllowDenyPolicyListener alloc] initWithPermissionRequestProxy:request.permissionRequest.get()]);
176        decidePolicyForGeolocationRequestFromOrigin(request.origin.get(), request.frame->url(), policyListener.get(), request.window.get());
177    }
178}
179
180- (void)geolocationAuthorizationDenied
181{
182    Vector<GeolocationRequestData> requests = WTF::move(_requestsWaitingForCoreLocationAuthorization);
183    for (const auto& requestData : requests)
184        requestData.permissionRequest->deny();
185}
186
187- (void)positionChanged:(WebCore::GeolocationPosition*)position
188{
189    _lastActivePosition = WebGeolocationPosition::create(position->timestamp(), position->latitude(), position->longitude(), position->accuracy(), position->canProvideAltitude(), position->altitude(), position->canProvideAltitudeAccuracy(), position->altitudeAccuracy(), position->canProvideHeading(), position->heading(), position->canProvideSpeed(), position->speed());
190    _geolocationManager->providerDidChangePosition(_lastActivePosition.get());
191}
192
193- (void)errorOccurred:(NSString *)errorMessage
194{
195    _geolocationManager->providerDidFailToDeterminePosition(errorMessage);
196}
197
198- (void)resetGeolocation
199{
200    _geolocationManager->resetPermissions();
201}
202
203@end
204
205# pragma mark - Implementation of WKWebAllowDenyPolicyListener
206@implementation WKWebAllowDenyPolicyListener {
207    RefPtr<GeolocationPermissionRequestProxy> _permissionRequestProxy;
208}
209
210- (id)initWithPermissionRequestProxy:(PassRefPtr<GeolocationPermissionRequestProxy>)permissionRequestProxy
211{
212    self = [super init];
213    if (!self)
214        return nil;
215
216    _permissionRequestProxy = permissionRequestProxy;
217    return self;
218}
219
220- (void)allow
221{
222    _permissionRequestProxy->allow();
223}
224
225- (void)deny
226{
227    _permissionRequestProxy->deny();
228}
229
230- (void)denyOnlyThisRequest
231{
232    // The method denyOnlyThisRequest is iAd specific for WebKit1.
233    ASSERT_NOT_REACHED();
234}
235
236- (BOOL)shouldClearCache
237{
238    return NO;
239}
240@end
241
242#pragma clang diagnostic pop
243
244#endif // PLATFORM(IOS)
245