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
26class GeolocationTest: public WebViewTest {
27public:
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
253static 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
300static 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
329void beforeAll()
330{
331 GeolocationTest::add("WebKitGeolocationManager", "current-position", testGeolocationManagerCurrentPosition);
332 GeolocationTest::add("WebKitGeolocationManager", "watch-position", testGeolocationManagerWatchPosition);
333}
334
335void afterAll()
336{
337}
338