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 | |