| 1 | /* |
| 2 | * Copyright (C) 2019 Igalia S.L. |
| 3 | * |
| 4 | * This library is free software; you can redistribute it and/or |
| 5 | * modify it under the terms of the GNU Lesser General Public |
| 6 | * License as published by the Free Software Foundation; either |
| 7 | * version 2,1 of the License, or (at your option) any later version. |
| 8 | * |
| 9 | * This library is distributed in the hope that it will be useful, |
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 12 | * Library General Public License for more details. |
| 13 | * |
| 14 | * You should have received a copy of the GNU Library General Public License |
| 15 | * along with this library; see the file COPYING.LIB. If not, write to |
| 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| 17 | * Boston, MA 02110-1301, USA. |
| 18 | */ |
| 19 | |
| 20 | #include "config.h" |
| 21 | #include "TestMain.h" |
| 22 | |
| 23 | #include "WebViewTest.h" |
| 24 | #include <wtf/WallTime.h> |
| 25 | |
| 26 | class GeolocationTest: public WebViewTest { |
| 27 | public: |
| 28 | MAKE_GLIB_TEST_FIXTURE(GeolocationTest); |
| 29 | |
| 30 | struct Position { |
| 31 | double timestamp { std::numeric_limits<double>::quiet_NaN() }; |
| 32 | double latitude { std::numeric_limits<double>::quiet_NaN() }; |
| 33 | double longitude { std::numeric_limits<double>::quiet_NaN() }; |
| 34 | double accuracy { std::numeric_limits<double>::quiet_NaN() }; |
| 35 | |
| 36 | Optional<double> altitude; |
| 37 | Optional<double> altitudeAccuracy; |
| 38 | Optional<double> heading; |
| 39 | Optional<double> speed; |
| 40 | }; |
| 41 | |
| 42 | static gboolean startCallback(WebKitGeolocationManager* manager, GeolocationTest* test) |
| 43 | { |
| 44 | g_assert_true(test->m_manager == manager); |
| 45 | test->start(); |
| 46 | return TRUE; |
| 47 | } |
| 48 | |
| 49 | static void stopCallback(WebKitGeolocationManager* manager, GeolocationTest* test) |
| 50 | { |
| 51 | g_assert_true(test->m_manager == manager); |
| 52 | test->stop(); |
| 53 | } |
| 54 | |
| 55 | static gboolean permissionRequested(WebKitWebView*, WebKitPermissionRequest* request, GeolocationTest* test) |
| 56 | { |
| 57 | g_assert_true(WEBKIT_IS_PERMISSION_REQUEST(request)); |
| 58 | test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(request)); |
| 59 | |
| 60 | if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(request)) { |
| 61 | webkit_permission_request_allow(request); |
| 62 | return TRUE; |
| 63 | } |
| 64 | |
| 65 | return FALSE; |
| 66 | } |
| 67 | |
| 68 | GeolocationTest() |
| 69 | : m_manager(webkit_web_context_get_geolocation_manager(m_webContext.get())) |
| 70 | { |
| 71 | g_assert_true(WEBKIT_IS_GEOLOCATION_MANAGER(m_manager)); |
| 72 | assertObjectIsDeletedWhenTestFinishes(G_OBJECT(m_manager)); |
| 73 | g_signal_connect(m_manager, "start" , G_CALLBACK(startCallback), this); |
| 74 | g_signal_connect(m_manager, "stop" , G_CALLBACK(stopCallback), this); |
| 75 | g_signal_connect(m_webView, "permission-request" , G_CALLBACK(permissionRequested), this); |
| 76 | } |
| 77 | |
| 78 | ~GeolocationTest() |
| 79 | { |
| 80 | if (m_checkPosition) |
| 81 | webkit_geolocation_position_free(m_checkPosition); |
| 82 | |
| 83 | g_signal_handlers_disconnect_matched(m_manager, G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this); |
| 84 | g_signal_handlers_disconnect_matched(m_webView, G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this); |
| 85 | } |
| 86 | |
| 87 | void start() |
| 88 | { |
| 89 | g_assert_false(m_updating); |
| 90 | m_updating = true; |
| 91 | |
| 92 | if (m_errorMessage) { |
| 93 | g_assert_false(m_checkPosition); |
| 94 | webkit_geolocation_manager_failed(m_manager, m_errorMessage.get()); |
| 95 | } |
| 96 | |
| 97 | if (m_checkPosition) { |
| 98 | g_assert_false(m_errorMessage.get()); |
| 99 | webkit_geolocation_manager_update_position(m_manager, m_checkPosition); |
| 100 | } |
| 101 | |
| 102 | g_main_loop_quit(m_mainLoop); |
| 103 | } |
| 104 | |
| 105 | void stop() |
| 106 | { |
| 107 | g_assert_true(m_updating); |
| 108 | m_updating = false; |
| 109 | if (m_watching) |
| 110 | g_main_loop_quit(m_mainLoop); |
| 111 | } |
| 112 | |
| 113 | Position lastPosition() |
| 114 | { |
| 115 | GUniqueOutPtr<GError> error; |
| 116 | auto* javascriptResult = runJavaScriptAndWaitUntilFinished("position" , &error.outPtr()); |
| 117 | g_assert_nonnull(javascriptResult); |
| 118 | g_assert_no_error(error.get()); |
| 119 | |
| 120 | auto* jsPosition = webkit_javascript_result_get_js_value(javascriptResult); |
| 121 | g_assert_true(jsc_value_is_object(jsPosition)); |
| 122 | Position result; |
| 123 | GRefPtr<JSCValue> value = adoptGRef(jsc_value_object_get_property(jsPosition, "latitude" )); |
| 124 | g_assert_true(jsc_value_is_number(value.get())); |
| 125 | result.latitude = jsc_value_to_double(value.get()); |
| 126 | value = adoptGRef(jsc_value_object_get_property(jsPosition, "longitude" )); |
| 127 | g_assert_true(jsc_value_is_number(value.get())); |
| 128 | result.longitude = jsc_value_to_double(value.get()); |
| 129 | value = adoptGRef(jsc_value_object_get_property(jsPosition, "accuracy" )); |
| 130 | g_assert_true(jsc_value_is_number(value.get())); |
| 131 | result.accuracy = jsc_value_to_double(value.get()); |
| 132 | value = adoptGRef(jsc_value_object_get_property(jsPosition, "altitude" )); |
| 133 | if (!jsc_value_is_null(value.get())) { |
| 134 | g_assert_true(jsc_value_is_number(value.get())); |
| 135 | result.altitude = jsc_value_to_double(value.get()); |
| 136 | } |
| 137 | value = adoptGRef(jsc_value_object_get_property(jsPosition, "altitudeAccuracy" )); |
| 138 | if (!jsc_value_is_null(value.get())) { |
| 139 | g_assert_true(jsc_value_is_number(value.get())); |
| 140 | result.altitudeAccuracy = jsc_value_to_double(value.get()); |
| 141 | } |
| 142 | value = adoptGRef(jsc_value_object_get_property(jsPosition, "heading" )); |
| 143 | if (!jsc_value_is_null(value.get())) { |
| 144 | g_assert_true(jsc_value_is_number(value.get())); |
| 145 | result.heading = jsc_value_to_double(value.get()); |
| 146 | } |
| 147 | value = adoptGRef(jsc_value_object_get_property(jsPosition, "speed" )); |
| 148 | if (!jsc_value_is_null(value.get())) { |
| 149 | g_assert_true(jsc_value_is_number(value.get())); |
| 150 | result.speed = jsc_value_to_double(value.get()); |
| 151 | } |
| 152 | value = adoptGRef(jsc_value_object_get_property(jsPosition, "timestamp" )); |
| 153 | g_assert_true(jsc_value_is_number(value.get())); |
| 154 | result.timestamp = jsc_value_to_double(value.get()); |
| 155 | return result; |
| 156 | } |
| 157 | |
| 158 | Position checkCurrentPosition(WebKitGeolocationPosition* position, bool enableHighAccuracy = false) |
| 159 | { |
| 160 | g_clear_pointer(&m_checkPosition, webkit_geolocation_position_free); |
| 161 | m_checkPosition = position; |
| 162 | |
| 163 | static const char getCurrentPosition[] = |
| 164 | "var position = null;\n" |
| 165 | "options = { enableHighAccuracy: %s }\n" |
| 166 | "navigator.geolocation.getCurrentPosition(function(p) {\n" |
| 167 | " position = { };\n" |
| 168 | " position.latitude = p.coords.latitude;\n" |
| 169 | " position.longitude = p.coords.longitude;\n" |
| 170 | " position.accuracy = p.coords.accuracy;\n" |
| 171 | " position.altitude = p.coords.altitude;\n" |
| 172 | " position.altitudeAccuracy = p.coords.altitudeAccuracy;\n" |
| 173 | " position.heading = p.coords.heading;\n" |
| 174 | " position.speed = p.coords.speed;\n" |
| 175 | " position.timestamp = p.timestamp;\n" |
| 176 | "}, undefined, options);" ; |
| 177 | GUniquePtr<char> script(g_strdup_printf(getCurrentPosition, enableHighAccuracy ? "true" : "false" )); |
| 178 | GUniqueOutPtr<GError> error; |
| 179 | runJavaScriptAndWaitUntilFinished(script.get(), &error.outPtr()); |
| 180 | g_assert_no_error(error.get()); |
| 181 | g_main_loop_run(m_mainLoop); |
| 182 | g_clear_pointer(&m_checkPosition, webkit_geolocation_position_free); |
| 183 | g_assert_true(webkit_geolocation_manager_get_enable_high_accuracy(m_manager) == enableHighAccuracy); |
| 184 | |
| 185 | return lastPosition(); |
| 186 | } |
| 187 | |
| 188 | GUniquePtr<char> checkFailedToDeterminePosition(const char* errorMessage) |
| 189 | { |
| 190 | m_errorMessage.reset(g_strdup(errorMessage)); |
| 191 | static const char getCurrentPosition[] = |
| 192 | "var error;\n" |
| 193 | "navigator.geolocation.getCurrentPosition(\n" |
| 194 | " function(p) { error = null; },\n" |
| 195 | " function(e) { error = e.message.toString(); }\n" |
| 196 | ");" ; |
| 197 | GUniqueOutPtr<GError> error; |
| 198 | auto* javascriptResult = runJavaScriptAndWaitUntilFinished(getCurrentPosition, &error.outPtr()); |
| 199 | g_assert_no_error(error.get()); |
| 200 | g_main_loop_run(m_mainLoop); |
| 201 | m_errorMessage = nullptr; |
| 202 | |
| 203 | javascriptResult = runJavaScriptAndWaitUntilFinished("error" , &error.outPtr()); |
| 204 | g_assert_nonnull(javascriptResult); |
| 205 | g_assert_no_error(error.get()); |
| 206 | auto* jsErrorMessage = webkit_javascript_result_get_js_value(javascriptResult); |
| 207 | g_assert_true(jsc_value_is_string(jsErrorMessage)); |
| 208 | return GUniquePtr<char>(jsc_value_to_string(jsErrorMessage)); |
| 209 | } |
| 210 | |
| 211 | void startWatch() |
| 212 | { |
| 213 | g_clear_pointer(&m_checkPosition, webkit_geolocation_position_free); |
| 214 | m_errorMessage = nullptr; |
| 215 | |
| 216 | m_watching = true; |
| 217 | static const char watchPosition[] = |
| 218 | "var position = null;\n" |
| 219 | "var watchID = navigator.geolocation.watchPosition(function(p) {\n" |
| 220 | " position = { };\n" |
| 221 | " position.latitude = p.coords.latitude;\n" |
| 222 | " position.longitude = p.coords.longitude;\n" |
| 223 | " position.accuracy = p.coords.accuracy;\n" |
| 224 | " position.altitude = p.coords.altitude;\n" |
| 225 | " position.altitudeAccuracy = p.coords.altitudeAccuracy;\n" |
| 226 | " position.heading = p.coords.heading;\n" |
| 227 | " position.speed = p.coords.speed;\n" |
| 228 | " position.timestamp = p.timestamp;\n" |
| 229 | "});" ; |
| 230 | GUniqueOutPtr<GError> error; |
| 231 | auto* javascriptResult = runJavaScriptAndWaitUntilFinished(watchPosition, &error.outPtr()); |
| 232 | g_assert_nonnull(javascriptResult); |
| 233 | g_assert_no_error(error.get()); |
| 234 | g_main_loop_run(m_mainLoop); |
| 235 | } |
| 236 | |
| 237 | void stopWatch() |
| 238 | { |
| 239 | GUniqueOutPtr<GError> error; |
| 240 | runJavaScriptAndWaitUntilFinished("navigator.geolocation.clearWatch(watchID)" , &error.outPtr()); |
| 241 | g_assert_no_error(error.get()); |
| 242 | g_main_loop_run(m_mainLoop); |
| 243 | m_watching = false; |
| 244 | } |
| 245 | |
| 246 | WebKitGeolocationManager* m_manager; |
| 247 | bool m_updating { false }; |
| 248 | bool m_watching { false }; |
| 249 | WebKitGeolocationPosition* m_checkPosition { nullptr }; |
| 250 | GUniquePtr<char> m_errorMessage; |
| 251 | }; |
| 252 | |
| 253 | static void testGeolocationManagerCurrentPosition(GeolocationTest* test, gconstpointer) |
| 254 | { |
| 255 | test->showInWindow(); |
| 256 | test->loadHtml("<html><body></body></html>" , "https://foo.com/bar" ); |
| 257 | test->waitUntilLoadFinished(); |
| 258 | |
| 259 | WebKitGeolocationPosition* position = webkit_geolocation_position_new(37.1760783, -3.59033, 17); |
| 260 | g_assert_false(test->m_updating); |
| 261 | auto result = test->checkCurrentPosition(position); |
| 262 | g_assert_false(test->m_updating); |
| 263 | g_assert_cmpfloat(result.latitude, ==, 37.1760783); |
| 264 | g_assert_cmpfloat(result.longitude, ==, -3.59033); |
| 265 | g_assert_cmpfloat(result.accuracy, ==, 17); |
| 266 | g_assert_false(result.altitude); |
| 267 | g_assert_false(result.altitudeAccuracy); |
| 268 | g_assert_false(result.heading); |
| 269 | g_assert_false(result.speed); |
| 270 | g_assert_cmpfloat(result.timestamp, >, 0); |
| 271 | g_assert_cmpfloat(result.timestamp / 1000., <, WallTime::now().secondsSinceEpoch().value()); |
| 272 | |
| 273 | position = webkit_geolocation_position_new(27.986065, 86.922623, 0); |
| 274 | auto timestamp = WallTime::now().secondsSinceEpoch(); |
| 275 | webkit_geolocation_position_set_timestamp(position, timestamp.value()); |
| 276 | webkit_geolocation_position_set_altitude(position, 8495); |
| 277 | webkit_geolocation_position_set_altitude_accuracy(position, 0); |
| 278 | webkit_geolocation_position_set_heading(position, 100.55); |
| 279 | webkit_geolocation_position_set_speed(position, 0.05); |
| 280 | g_assert_false(test->m_updating); |
| 281 | result = test->checkCurrentPosition(position, true); |
| 282 | g_assert_false(test->m_updating); |
| 283 | g_assert_cmpfloat(result.latitude, ==, 27.986065); |
| 284 | g_assert_cmpfloat(result.longitude, ==, 86.922623); |
| 285 | g_assert_cmpfloat(result.accuracy, ==, 0); |
| 286 | g_assert_true(result.altitude); |
| 287 | g_assert_cmpfloat(result.altitude.value(), ==, 8495); |
| 288 | g_assert_true(result.altitudeAccuracy); |
| 289 | g_assert_cmpfloat(result.altitudeAccuracy.value(), ==, 0); |
| 290 | g_assert_true(result.heading); |
| 291 | g_assert_cmpfloat(result.heading.value(), ==, 100.55); |
| 292 | g_assert_true(result.speed); |
| 293 | g_assert_cmpfloat(result.speed.value(), ==, 0.05); |
| 294 | g_assert_cmpfloat_with_epsilon(result.timestamp / 1000., timestamp.value(), 1); |
| 295 | |
| 296 | auto errorMessage = test->checkFailedToDeterminePosition("GPS is not active" ); |
| 297 | g_assert_cmpstr(errorMessage.get(), ==, "GPS is not active" ); |
| 298 | } |
| 299 | |
| 300 | static void testGeolocationManagerWatchPosition(GeolocationTest* test, gconstpointer) |
| 301 | { |
| 302 | test->showInWindow(); |
| 303 | test->loadHtml("<html><body></body></html>" , "https://foo.com/bar" ); |
| 304 | test->waitUntilLoadFinished(); |
| 305 | |
| 306 | test->startWatch(); |
| 307 | g_assert_true(test->m_updating); |
| 308 | |
| 309 | WebKitGeolocationPosition* position = webkit_geolocation_position_new(37.1760783, -3.59033, 17); |
| 310 | webkit_geolocation_manager_update_position(test->m_manager, position); |
| 311 | webkit_geolocation_position_free(position); |
| 312 | auto result = test->lastPosition(); |
| 313 | g_assert_cmpfloat(result.latitude, ==, 37.1760783); |
| 314 | g_assert_cmpfloat(result.longitude, ==, -3.59033); |
| 315 | g_assert_cmpfloat(result.accuracy, ==, 17); |
| 316 | |
| 317 | position = webkit_geolocation_position_new(38.1770783, -3.60033, 17); |
| 318 | webkit_geolocation_manager_update_position(test->m_manager, position); |
| 319 | webkit_geolocation_position_free(position); |
| 320 | result = test->lastPosition(); |
| 321 | g_assert_cmpfloat(result.latitude, ==, 38.1770783); |
| 322 | g_assert_cmpfloat(result.longitude, ==, -3.60033); |
| 323 | g_assert_cmpfloat(result.accuracy, ==, 17); |
| 324 | |
| 325 | test->stopWatch(); |
| 326 | g_assert_false(test->m_updating); |
| 327 | } |
| 328 | |
| 329 | void beforeAll() |
| 330 | { |
| 331 | GeolocationTest::add("WebKitGeolocationManager" , "current-position" , testGeolocationManagerCurrentPosition); |
| 332 | GeolocationTest::add("WebKitGeolocationManager" , "watch-position" , testGeolocationManagerWatchPosition); |
| 333 | } |
| 334 | |
| 335 | void afterAll() |
| 336 | { |
| 337 | } |
| 338 | |