| 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 | |
| 38 | using namespace std; |
| 39 | |
| 40 | namespace TestWebKitAPI { |
| 41 | |
| 42 | enum class GeolocationEvent { |
| 43 | StartUpdating, |
| 44 | StopUpdating, |
| 45 | EnableHighAccuracy, |
| 46 | DisableHighAccuracy |
| 47 | }; |
| 48 | |
| 49 | ostream& 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 | |
| 68 | struct 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 | |
| 102 | void decidePolicyForGeolocationPermissionRequestCallBack(WKPageRef page, WKFrameRef frame, WKSecurityOriginRef origin, WKGeolocationPermissionRequestRef permissionRequest, const void* clientInfo) |
| 103 | { |
| 104 | WKGeolocationPermissionRequestAllow(permissionRequest); |
| 105 | } |
| 106 | |
| 107 | void 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 | |
| 121 | void clearGeolocationProvider(WKContextRef context) |
| 122 | { |
| 123 | WKGeolocationManagerSetProvider(WKContextGetGeolocationManager(context), nullptr); |
| 124 | } |
| 125 | |
| 126 | void 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. |
| 138 | struct 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 | |
| 162 | TEST(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. |
| 180 | struct 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 | |
| 204 | TEST(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. |
| 222 | struct 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 | |
| 257 | TEST(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. |
| 286 | struct 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 | |
| 318 | struct JavaScriptAlertContext { |
| 319 | bool didRun { false }; |
| 320 | std::string alertText; |
| 321 | }; |
| 322 | |
| 323 | static 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 | |
| 330 | TEST(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 | |
| 372 | TEST(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 | |