1/*
2 * Copyright (C) 2013 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#include "config.h"
27
28#if WK_HAVE_C_SPI
29
30#include "PlatformUtilities.h"
31#include "PlatformWebView.h"
32#include "Test.h"
33#include <WebKit/WKContextPrivate.h>
34#include <WebKit/WKRetainPtr.h>
35#include <string.h>
36#include <vector>
37
38using namespace std;
39
40namespace TestWebKitAPI {
41
42enum class GeolocationEvent {
43 StartUpdating,
44 StopUpdating,
45 EnableHighAccuracy,
46 DisableHighAccuracy
47};
48
49ostream& operator<<(ostream& outputStream, const GeolocationEvent& geolocationEvent)
50{
51 switch (geolocationEvent) {
52 case GeolocationEvent::StartUpdating:
53 outputStream << "GeolocationEvent::StartUpdating";
54 break;
55 case GeolocationEvent::StopUpdating:
56 outputStream << "GeolocationEvent::StopUpdating";
57 break;
58 case GeolocationEvent::EnableHighAccuracy:
59 outputStream << "GeolocationEvent::EnableHighAccuracy";
60 break;
61 case GeolocationEvent::DisableHighAccuracy:
62 outputStream << "GeolocationEvent::DisableHighAccuracy";
63 break;
64 }
65 return outputStream;
66}
67
68struct GeolocationStateTracker {
69 vector<GeolocationEvent> events;
70
71 virtual ~GeolocationStateTracker() { }
72 virtual void eventsChanged() { }
73
74 static void startUpdatingCallback(WKGeolocationManagerRef manager, const void* clientInfo)
75 {
76 GeolocationStateTracker* stateTracker = static_cast<GeolocationStateTracker*>(const_cast<void*>(clientInfo));
77 stateTracker->events.push_back(GeolocationEvent::StartUpdating);
78 stateTracker->eventsChanged();
79
80 WKRetainPtr<WKGeolocationPositionRef> position = adoptWK(WKGeolocationPositionCreate(0, 50.644358, 3.345453, 2.53));
81 WKGeolocationManagerProviderDidChangePosition(manager, position.get());
82 }
83
84 static void stopUpdatingCallback(WKGeolocationManagerRef, const void* clientInfo)
85 {
86 GeolocationStateTracker* stateTracker = static_cast<GeolocationStateTracker*>(const_cast<void*>(clientInfo));
87 stateTracker->events.push_back(GeolocationEvent::StopUpdating);
88 stateTracker->eventsChanged();
89 }
90
91 static void setEnableHighAccuracyCallback(WKGeolocationManagerRef, bool enable, const void* clientInfo)
92 {
93 GeolocationStateTracker* stateTracker = static_cast<GeolocationStateTracker*>(const_cast<void*>(clientInfo));
94 if (enable)
95 stateTracker->events.push_back(GeolocationEvent::EnableHighAccuracy);
96 else
97 stateTracker->events.push_back(GeolocationEvent::DisableHighAccuracy);
98 stateTracker->eventsChanged();
99 }
100};
101
102void decidePolicyForGeolocationPermissionRequestCallBack(WKPageRef page, WKFrameRef frame, WKSecurityOriginRef origin, WKGeolocationPermissionRequestRef permissionRequest, const void* clientInfo)
103{
104 WKGeolocationPermissionRequestAllow(permissionRequest);
105}
106
107void setupGeolocationProvider(WKContextRef context, void* clientInfo)
108{
109 WKGeolocationProviderV1 providerCallback;
110 memset(&providerCallback, 0, sizeof(WKGeolocationProviderV1));
111
112 providerCallback.base.version = 1;
113 providerCallback.base.clientInfo = clientInfo;
114 providerCallback.startUpdating = GeolocationStateTracker::startUpdatingCallback;
115 providerCallback.stopUpdating = GeolocationStateTracker::stopUpdatingCallback;
116 providerCallback.setEnableHighAccuracy = GeolocationStateTracker::setEnableHighAccuracyCallback;
117
118 WKGeolocationManagerSetProvider(WKContextGetGeolocationManager(context), &providerCallback.base);
119}
120
121void clearGeolocationProvider(WKContextRef context)
122{
123 WKGeolocationManagerSetProvider(WKContextGetGeolocationManager(context), nullptr);
124}
125
126void setupView(PlatformWebView& webView)
127{
128 WKPageUIClientV2 uiClient;
129 memset(&uiClient, 0, sizeof(uiClient));
130
131 uiClient.base.version = 2;
132 uiClient.decidePolicyForGeolocationPermissionRequest = decidePolicyForGeolocationPermissionRequestCallBack;
133
134 WKPageSetPageUIClient(webView.page(), &uiClient.base);
135}
136
137// GeolocationBasic.
138struct GeolocationBasicStateTracker : GeolocationStateTracker {
139 bool finished;
140
141 GeolocationBasicStateTracker() : finished(false) { }
142 virtual void eventsChanged()
143 {
144 switch (events.size()) {
145 case 1:
146 EXPECT_EQ(GeolocationEvent::DisableHighAccuracy, events[0]);
147 break;
148 case 2:
149 EXPECT_EQ(GeolocationEvent::StartUpdating, events[1]);
150 break;
151 case 3:
152 EXPECT_EQ(GeolocationEvent::StopUpdating, events[2]);
153 finished = true;
154 break;
155 default:
156 EXPECT_TRUE(false);
157 finished = true;
158 }
159 }
160};
161
162TEST(WebKit, GeolocationBasic)
163{
164 WKRetainPtr<WKContextRef> context = adoptWK(WKContextCreateWithConfiguration(nullptr));
165
166 GeolocationBasicStateTracker stateTracker;
167 setupGeolocationProvider(context.get(), &stateTracker);
168
169 PlatformWebView webView(context.get());
170 setupView(webView);
171
172 WKRetainPtr<WKURLRef> url = adoptWK(Util::createURLForResource("geolocationGetCurrentPosition", "html"));
173 WKPageLoadURL(webView.page(), url.get());
174
175 Util::run(&stateTracker.finished);
176 clearGeolocationProvider(context.get());
177}
178
179// Geolocation requested with High Accuracy.
180struct GeolocationBasicWithHighAccuracyStateTracker : GeolocationStateTracker {
181 bool finished;
182
183 GeolocationBasicWithHighAccuracyStateTracker() : finished(false) { }
184 virtual void eventsChanged()
185 {
186 switch (events.size()) {
187 case 1:
188 EXPECT_EQ(GeolocationEvent::EnableHighAccuracy, events[0]);
189 break;
190 case 2:
191 EXPECT_EQ(GeolocationEvent::StartUpdating, events[1]);
192 break;
193 case 3:
194 EXPECT_EQ(GeolocationEvent::StopUpdating, events[2]);
195 finished = true;
196 break;
197 default:
198 EXPECT_TRUE(false);
199 finished = true;
200 }
201 }
202};
203
204TEST(WebKit, GeolocationBasicWithHighAccuracy)
205{
206 WKRetainPtr<WKContextRef> context = adoptWK(WKContextCreateWithConfiguration(nullptr));
207
208 GeolocationBasicWithHighAccuracyStateTracker stateTracker;
209 setupGeolocationProvider(context.get(), &stateTracker);
210
211 PlatformWebView webView(context.get());
212 setupView(webView);
213
214 WKRetainPtr<WKURLRef> url = adoptWK(Util::createURLForResource("geolocationGetCurrentPositionWithHighAccuracy", "html"));
215 WKPageLoadURL(webView.page(), url.get());
216
217 Util::run(&stateTracker.finished);
218 clearGeolocationProvider(context.get());
219}
220
221// Geolocation start without High Accuracy, then requires High Accuracy.
222struct GeolocationTransitionToHighAccuracyStateTracker : GeolocationStateTracker {
223 bool finishedFirstStep { false };
224 bool enabledHighAccuracy { false };
225 bool finished { false };
226
227 virtual void eventsChanged()
228 {
229 switch (events.size()) {
230 case 1:
231 EXPECT_EQ(GeolocationEvent::DisableHighAccuracy, events[0]);
232 break;
233 case 2:
234 EXPECT_EQ(GeolocationEvent::StartUpdating, events[1]);
235 finishedFirstStep = true;
236 break;
237 case 3:
238 EXPECT_EQ(GeolocationEvent::EnableHighAccuracy, events[2]);
239 enabledHighAccuracy = true;
240 break;
241 case 4:
242 EXPECT_EQ(GeolocationEvent::DisableHighAccuracy, events[3]);
243 break;
244 case 5:
245 EXPECT_EQ(GeolocationEvent::StopUpdating, events[4]);
246 finished = true;
247 break;
248 default:
249 EXPECT_TRUE(false);
250 finishedFirstStep = true;
251 enabledHighAccuracy = true;
252 finished = true;
253 }
254 }
255};
256
257TEST(WebKit, GeolocationTransitionToHighAccuracy)
258{
259 WKRetainPtr<WKContextRef> context = adoptWK(WKContextCreateWithConfiguration(nullptr));
260
261 GeolocationTransitionToHighAccuracyStateTracker stateTracker;
262 setupGeolocationProvider(context.get(), &stateTracker);
263
264 PlatformWebView lowAccuracyWebView(context.get());
265 setupView(lowAccuracyWebView);
266 WKRetainPtr<WKURLRef> lowAccuracyURL = adoptWK(Util::createURLForResource("geolocationWatchPosition", "html"));
267 WKPageLoadURL(lowAccuracyWebView.page(), lowAccuracyURL.get());
268 Util::run(&stateTracker.finishedFirstStep);
269
270 PlatformWebView highAccuracyWebView(lowAccuracyWebView.page());
271 setupView(highAccuracyWebView);
272 WKRetainPtr<WKURLRef> highAccuracyURL = adoptWK(Util::createURLForResource("geolocationWatchPositionWithHighAccuracy", "html"));
273 WKPageLoadURL(highAccuracyWebView.page(), highAccuracyURL.get());
274 Util::run(&stateTracker.enabledHighAccuracy);
275
276 WKRetainPtr<WKURLRef> resetUrl = adoptWK(WKURLCreateWithUTF8CString("about:blank"));
277 WKPageLoadURL(highAccuracyWebView.page(), resetUrl.get());
278 Util::run(&stateTracker.enabledHighAccuracy);
279 WKPageLoadURL(lowAccuracyWebView.page(), resetUrl.get());
280 Util::run(&stateTracker.finished);
281
282 clearGeolocationProvider(context.get());
283}
284
285// Geolocation start with High Accuracy, then should fall back to low accuracy.
286struct GeolocationTransitionToLowAccuracyStateTracker : GeolocationStateTracker {
287 bool finishedFirstStep { false };
288 bool disabledHighAccuracy { false };
289 bool finished { false };
290
291 virtual void eventsChanged()
292 {
293 switch (events.size()) {
294 case 1:
295 EXPECT_EQ(GeolocationEvent::EnableHighAccuracy, events[0]);
296 break;
297 case 2:
298 EXPECT_EQ(GeolocationEvent::StartUpdating, events[1]);
299 finishedFirstStep = true;
300 break;
301 case 3:
302 EXPECT_EQ(GeolocationEvent::DisableHighAccuracy, events[2]);
303 disabledHighAccuracy = true;
304 break;
305 case 4:
306 EXPECT_EQ(GeolocationEvent::StopUpdating, events[3]);
307 finished = true;
308 break;
309 default:
310 EXPECT_TRUE(false);
311 finishedFirstStep = true;
312 disabledHighAccuracy = true;
313 finished = true;
314 }
315 }
316};
317
318struct JavaScriptAlertContext {
319 bool didRun { false };
320 std::string alertText;
321};
322
323static void runJavaScriptAlert(WKPageRef page, WKStringRef alertText, WKFrameRef frame, const void* clientInfo)
324{
325 auto* context = static_cast<JavaScriptAlertContext*>(const_cast<void*>(clientInfo));
326 context->didRun = true;
327 context->alertText = Util::toSTD(alertText);
328}
329
330TEST(WebKit, GeolocationTransitionToLowAccuracy)
331{
332 WKRetainPtr<WKContextRef> context = adoptWK(WKContextCreateWithConfiguration(nullptr));
333
334 GeolocationTransitionToLowAccuracyStateTracker stateTracker;
335 setupGeolocationProvider(context.get(), &stateTracker);
336
337 PlatformWebView highAccuracyWebView(context.get());
338 setupView(highAccuracyWebView);
339 WKRetainPtr<WKURLRef> highAccuracyURL = adoptWK(Util::createURLForResource("geolocationWatchPositionWithHighAccuracy", "html"));
340 WKPageLoadURL(highAccuracyWebView.page(), highAccuracyURL.get());
341 Util::run(&stateTracker.finishedFirstStep);
342
343 PlatformWebView lowAccuracyWebView(context.get());
344 setupView(lowAccuracyWebView);
345
346 JavaScriptAlertContext secondStepContext;
347
348 WKPageUIClientV2 uiClient;
349 memset(&uiClient, 0, sizeof(uiClient));
350 uiClient.base.version = 2;
351 uiClient.base.clientInfo = &secondStepContext;
352 uiClient.decidePolicyForGeolocationPermissionRequest = decidePolicyForGeolocationPermissionRequestCallBack;
353 uiClient.runJavaScriptAlert = runJavaScriptAlert;
354 WKPageSetPageUIClient(lowAccuracyWebView.page(), &uiClient.base);
355
356 WKRetainPtr<WKURLRef> lowAccuracyURL = adoptWK(Util::createURLForResource("geolocationWatchPosition", "html"));
357 WKPageLoadURL(lowAccuracyWebView.page(), lowAccuracyURL.get());
358 Util::run(&secondStepContext.didRun);
359 EXPECT_EQ(secondStepContext.alertText, "SUCCESS");
360
361 WKRetainPtr<WKURLRef> resetUrl = adoptWK(WKURLCreateWithUTF8CString("about:blank"));
362 WKPageLoadURL(highAccuracyWebView.page(), resetUrl.get());
363
364 Util::run(&stateTracker.disabledHighAccuracy);
365
366 WKPageLoadURL(lowAccuracyWebView.page(), resetUrl.get());
367 Util::run(&stateTracker.finished);
368
369 clearGeolocationProvider(context.get());
370}
371
372TEST(WebKit, GeolocationWatchMultiprocess)
373{
374 WKRetainPtr<WKContextRef> context = adoptWK(WKContextCreateWithConfiguration(nullptr));
375
376 GeolocationStateTracker stateTracker;
377 setupGeolocationProvider(context.get(), &stateTracker);
378
379 JavaScriptAlertContext testContext;
380
381 WKPageUIClientV2 uiClient;
382 memset(&uiClient, 0, sizeof(uiClient));
383 uiClient.base.version = 2;
384 uiClient.base.clientInfo = &testContext;
385 uiClient.decidePolicyForGeolocationPermissionRequest = decidePolicyForGeolocationPermissionRequestCallBack;
386 uiClient.runJavaScriptAlert = runJavaScriptAlert;
387
388 PlatformWebView view1(context.get());
389 WKPageSetPageUIClient(view1.page(), &uiClient.base);
390 WKRetainPtr<WKURLRef> url = adoptWK(Util::createURLForResource("geolocationWatchPosition", "html"));
391 WKPageLoadURL(view1.page(), url.get());
392 Util::run(&testContext.didRun);
393 EXPECT_EQ(testContext.alertText, "SUCCESS");
394 WKPageSetPageUIClient(view1.page(), nullptr);
395
396 testContext.didRun = false;
397 testContext.alertText = { };
398
399 PlatformWebView view2(context.get());
400 WKPageSetPageUIClient(view2.page(), &uiClient.base);
401 WKPageLoadURL(view2.page(), url.get());
402 Util::run(&testContext.didRun);
403 EXPECT_EQ(testContext.alertText, "SUCCESS");
404
405 clearGeolocationProvider(context.get());
406}
407
408} // namespace TestWebKitAPI
409
410#endif
411