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 Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 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 "WebKitGeolocationManager.h"
22
23#include "APIGeolocationProvider.h"
24#include "GeoclueGeolocationProvider.h"
25#include "WebGeolocationPosition.h"
26#include "WebKitGeolocationManagerPrivate.h"
27#include <glib/gi18n-lib.h>
28#include <wtf/WallTime.h>
29#include <wtf/glib/WTFGType.h>
30
31using namespace WebKit;
32using namespace WebCore;
33
34/**
35 * SECTION:WebKitGeolocationManager
36 * @short_description: WebKitGeolocationManager
37 * @title: Geolocation manager
38 * @see_also: #WebKitGeolocationPermissionRequest, #WebKitWebContext
39 *
40 * WebKitGeolocationManager provides API to get the geographical position of the user.
41 * Once a #WebKitGeolocationPermissionRequest is allowed, when WebKit needs to know the
42 * user location #WebKitGeolocationManager::start signal is emitted. If the signal is handled
43 * and returns %TRUE, the application is responsible for providing the position every time it's
44 * updated by calling webkit_geolocation_manager_update_position(). The signal #WebKitGeolocationManager::stop
45 * will be emitted when location updates are no longer needed.
46 *
47 * Since: 2.26
48 */
49
50enum {
51 PROP_0,
52
53 PROP_ENABLE_HIGH_ACCURACY
54};
55
56enum {
57 START,
58 STOP,
59 LAST_SIGNAL
60};
61
62struct _WebKitGeolocationPosition {
63 _WebKitGeolocationPosition() = default;
64
65 _WebKitGeolocationPosition(double latitude, double longitude, double accuracy)
66 {
67 position.timestamp = WallTime::now().secondsSinceEpoch().value();
68 position.latitude = latitude;
69 position.longitude = longitude;
70 position.accuracy = accuracy;
71 }
72
73 explicit _WebKitGeolocationPosition(GeolocationPosition&& corePosition)
74 : position(WTFMove(corePosition))
75 {
76 }
77
78 explicit _WebKitGeolocationPosition(const GeolocationPosition& other)
79 {
80 position = other;
81 }
82
83 GeolocationPosition position;
84};
85
86/**
87 * WebKitGeolocationPosition:
88 *
89 * WebKitGeolocationPosition is an opaque struct used to provide position updates to a
90 * #WebKitGeolocationManager using webkit_geolocation_manager_update_position().
91 *
92 * Since: 2.26
93 */
94
95G_DEFINE_BOXED_TYPE(WebKitGeolocationPosition, webkit_geolocation_position, webkit_geolocation_position_copy, webkit_geolocation_position_free)
96
97/**
98 * webkit_geolocation_position_new:
99 * @latitude: a valid latitude in degrees
100 * @longitude: a valid longitude in degrees
101 * @accuracy: accuracy of location in meters
102 *
103 * Create a new #WebKitGeolocationPosition
104 *
105 * Returns: (transfer full): a newly created #WebKitGeolocationPosition
106 *
107 * Since: 2.26
108 */
109WebKitGeolocationPosition* webkit_geolocation_position_new(double latitude, double longitude, double accuracy)
110{
111 auto* position = static_cast<WebKitGeolocationPosition*>(fastMalloc(sizeof(WebKitGeolocationPosition)));
112 new (position) WebKitGeolocationPosition(latitude, longitude, accuracy);
113 return position;
114}
115
116/**
117 * webkit_geolocation_position_copy:
118 * @position: a #WebKitGeolocationPosition
119 *
120 * Make a copy of the #WebKitGeolocationPosition
121 *
122 * Returns: (transfer full): a copy of @position
123 *
124 * Since: 2.26
125 */
126WebKitGeolocationPosition* webkit_geolocation_position_copy(WebKitGeolocationPosition* position)
127{
128 g_return_val_if_fail(position, nullptr);
129
130 auto* copy = static_cast<WebKitGeolocationPosition*>(fastMalloc(sizeof(WebKitGeolocationPosition)));
131 new (copy) WebKitGeolocationPosition(position->position);
132 return copy;
133}
134
135/**
136 * webkit_geolocation_position_free:
137 * @position: a #WebKitGeolocationPosition
138 *
139 * Free the #WebKitGeolocationPosition
140 *
141 * Since: 2.26
142 */
143void webkit_geolocation_position_free(WebKitGeolocationPosition* position)
144{
145 g_return_if_fail(position);
146
147 position->~WebKitGeolocationPosition();
148 fastFree(position);
149}
150
151/**
152 * webkit_geolocation_position_set_timestamp:
153 * @position: a #WebKitGeolocationPosition
154 * @timestamp: timestamp in seconds since the epoch, or 0 to use current time
155 *
156 * Set the @position timestamp. By default it's the time when the @position was created.
157 *
158 * Since: 2.26
159 */
160void webkit_geolocation_position_set_timestamp(WebKitGeolocationPosition* position, guint64 timestamp)
161{
162 g_return_if_fail(position);
163
164 position->position.timestamp = timestamp ? static_cast<double>(timestamp) : WallTime::now().secondsSinceEpoch().value();
165}
166
167/**
168 * webkit_geolocation_position_set_altitude:
169 * @position: a #WebKitGeolocationPosition
170 * @altitude: altitude in meters
171 *
172 * Set the @position altitude
173 *
174 * Since: 2.26
175 */
176void webkit_geolocation_position_set_altitude(WebKitGeolocationPosition* position, double altitude)
177{
178 g_return_if_fail(position);
179
180 position->position.altitude = altitude;
181}
182
183/**
184 * webkit_geolocation_position_set_altitude_accuracy:
185 * @position: a #WebKitGeolocationPosition
186 * @altitude_accuracy: accuracy of position altitude in meters
187 *
188 * Set the accuracy of @position altitude
189 *
190 * Since: 2.26
191 */
192void webkit_geolocation_position_set_altitude_accuracy(WebKitGeolocationPosition* position, double altitudeAccuracy)
193{
194 g_return_if_fail(position);
195
196 position->position.altitudeAccuracy = altitudeAccuracy;
197}
198
199/**
200 * webkit_geolocation_position_set_heading:
201 * @position: a #WebKitGeolocationPosition
202 * @heading: heading in degrees
203 *
204 * Set the @position heading, as a positive angle between the direction of movement and the North
205 * direction, in clockwise direction.
206 *
207 * Since: 2.26
208 */
209void webkit_geolocation_position_set_heading(WebKitGeolocationPosition* position, double heading)
210{
211 g_return_if_fail(position);
212
213 position->position.heading = heading;
214}
215
216/**
217 * webkit_geolocation_position_set_speed:
218 * @position: a #WebKitGeolocationPosition
219 * @speed: speed in meters per second
220 *
221 * Set the @position speed
222 *
223 * Since: 2.26
224 */
225void webkit_geolocation_position_set_speed(WebKitGeolocationPosition* position, double speed)
226{
227 g_return_if_fail(position);
228
229 position->position.speed = speed;
230}
231
232struct _WebKitGeolocationManagerPrivate {
233 RefPtr<WebGeolocationManagerProxy> manager;
234 bool highAccuracyEnabled;
235 std::unique_ptr<GeoclueGeolocationProvider> geoclueProvider;
236};
237
238static guint signals[LAST_SIGNAL] = { 0, };
239
240WEBKIT_DEFINE_TYPE(WebKitGeolocationManager, webkit_geolocation_manager, G_TYPE_OBJECT)
241
242static void webkitGeolocationManagerStart(WebKitGeolocationManager* manager)
243{
244 gboolean returnValue;
245 g_signal_emit(manager, signals[START], 0, &returnValue);
246 if (returnValue) {
247 manager->priv->geoclueProvider = nullptr;
248 return;
249 }
250
251 if (!manager->priv->geoclueProvider) {
252 manager->priv->geoclueProvider = std::make_unique<GeoclueGeolocationProvider>();
253 manager->priv->geoclueProvider->setEnableHighAccuracy(manager->priv->highAccuracyEnabled);
254 }
255 manager->priv->geoclueProvider->start([manager](GeolocationPosition&& corePosition, Optional<CString> error) {
256 if (error) {
257 webkit_geolocation_manager_failed(manager, error->data());
258 return;
259 }
260
261 WebKitGeolocationPosition position(WTFMove(corePosition));
262 webkit_geolocation_manager_update_position(manager, &position);
263 });
264}
265
266static void webkitGeolocationManagerStop(WebKitGeolocationManager* manager)
267{
268 g_signal_emit(manager, signals[STOP], 0, nullptr);
269
270 if (manager->priv->geoclueProvider)
271 manager->priv->geoclueProvider->stop();
272}
273
274static void webkitGeolocationManagerSetEnableHighAccuracy(WebKitGeolocationManager* manager, bool enabled)
275{
276 if (manager->priv->highAccuracyEnabled == enabled)
277 return;
278
279 manager->priv->highAccuracyEnabled = enabled;
280 g_object_notify(G_OBJECT(manager), "enable-high-accuracy");
281 if (manager->priv->geoclueProvider)
282 manager->priv->geoclueProvider->setEnableHighAccuracy(enabled);
283}
284
285class GeolocationProvider final : public API::GeolocationProvider {
286public:
287 explicit GeolocationProvider(WebKitGeolocationManager* manager)
288 : m_manager(manager)
289 {
290 }
291
292private:
293 void startUpdating(WebGeolocationManagerProxy&) override
294 {
295 webkitGeolocationManagerStart(m_manager);
296 }
297
298 void stopUpdating(WebGeolocationManagerProxy&) override
299 {
300 webkitGeolocationManagerStop(m_manager);
301 }
302
303 void setEnableHighAccuracy(WebGeolocationManagerProxy&, bool enabled) override
304 {
305 webkitGeolocationManagerSetEnableHighAccuracy(m_manager, enabled);
306 }
307
308 WebKitGeolocationManager* m_manager;
309};
310
311WebKitGeolocationManager* webkitGeolocationManagerCreate(WebGeolocationManagerProxy* proxy)
312{
313 auto* manager = WEBKIT_GEOLOCATION_MANAGER(g_object_new(WEBKIT_TYPE_GEOLOCATION_MANAGER, nullptr));
314 manager->priv->manager = proxy;
315 proxy->setProvider(std::make_unique<GeolocationProvider>(manager));
316 return manager;
317}
318
319static void webkitGeolocationManagerGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec)
320{
321 WebKitGeolocationManager* manager = WEBKIT_GEOLOCATION_MANAGER(object);
322
323 switch (propId) {
324 case PROP_ENABLE_HIGH_ACCURACY:
325 g_value_set_boolean(value, webkit_geolocation_manager_get_enable_high_accuracy(manager));
326 break;
327 default:
328 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
329 }
330}
331
332static void webkit_geolocation_manager_class_init(WebKitGeolocationManagerClass* geolocationManagerClass)
333{
334 GObjectClass* gObjectClass = G_OBJECT_CLASS(geolocationManagerClass);
335 gObjectClass->get_property = webkitGeolocationManagerGetProperty;
336
337 /**
338 * WebKitGeolocationManager:enable-high-accuracy:
339 *
340 * Whether high accuracy is enabled. This is a read-only property that will be
341 * set to %TRUE when a #WebKitGeolocationManager needs to get accurate position updates.
342 * You can connect to notify::enable-high-accuracy signal to monitor it.
343 *
344 * Since: 2.26
345 */
346 g_object_class_install_property(
347 gObjectClass,
348 PROP_ENABLE_HIGH_ACCURACY,
349 g_param_spec_boolean(
350 "enable-high-accuracy",
351 _("Enable high accuracy"),
352 _("Whether high accuracy is enabled"),
353 FALSE,
354 WEBKIT_PARAM_READABLE));
355
356 /**
357 * WebKitGeolocationManager::start:
358 * @manager: the #WebKitGeolocationManager on which the signal is emitted
359 *
360 * The signal is emitted to notify that @manager needs to start receiving
361 * position updates. After this signal is emitted the user should provide
362 * the updates using webkit_geolocation_manager_update_position() every time
363 * the position changes, or use webkit_geolocation_manager_failed() in case
364 * it isn't possible to determine the current position.
365 *
366 * If the signal is not handled, WebKit will try to determine the position
367 * using GeoClue if available.
368 *
369 * Returns: %TRUE to stop other handlers from being invoked for the event.
370 * %FALSE to propagate the event further.
371 *
372 * Since: 2.26
373 */
374 signals[START] = g_signal_new(
375 "start",
376 G_TYPE_FROM_CLASS(geolocationManagerClass),
377 G_SIGNAL_RUN_LAST,
378 0,
379 g_signal_accumulator_true_handled, nullptr,
380 g_cclosure_marshal_generic,
381 G_TYPE_BOOLEAN, 0);
382
383 /**
384 * WebKitGeolocationManager::stop:
385 * @manager: the #WebKitGeolocationManager on which the signal is emitted
386 *
387 * The signal is emitted to notify that @manager doesn't need to receive
388 * position updates anymore.
389 *
390 * Since: 2.26
391 */
392 signals[STOP] = g_signal_new(
393 "stop",
394 G_TYPE_FROM_CLASS(geolocationManagerClass),
395 G_SIGNAL_RUN_LAST,
396 0,
397 nullptr, nullptr,
398 g_cclosure_marshal_generic,
399 G_TYPE_NONE, 0);
400}
401
402/**
403 * webkit_geolocation_manager_update_position:
404 * @manager: a #WebKitGeolocationManager
405 * @position: a #WebKitGeolocationPosition
406 *
407 * Notify @manager that position has been updated to @position.
408 *
409 * Since: 2.26
410 */
411void webkit_geolocation_manager_update_position(WebKitGeolocationManager* manager, WebKitGeolocationPosition* position)
412{
413 g_return_if_fail(WEBKIT_IS_GEOLOCATION_MANAGER(manager));
414 g_return_if_fail(position);
415
416 GeolocationPosition corePosition = position->position;
417 auto wkPosition = WebGeolocationPosition::create(WTFMove(corePosition));
418 manager->priv->manager->providerDidChangePosition(wkPosition.ptr());
419}
420
421/**
422 * webkit_geolocation_manager_failed:
423 * @manager: a #WebKitGeolocationManager
424 * @error_message: the error message
425 *
426 * Notify @manager that determining the position failed.
427 *
428 * Since: 2.26
429 */
430void webkit_geolocation_manager_failed(WebKitGeolocationManager* manager, const char* errorMessage)
431{
432 g_return_if_fail(WEBKIT_IS_GEOLOCATION_MANAGER(manager));
433
434 manager->priv->manager->providerDidFailToDeterminePosition(String::fromUTF8(errorMessage));
435}
436
437/**
438 * webkit_geolocation_manager_get_enable_high_accuracy:
439 * @manager: a #WebKitGeolocationManager
440 *
441 * Get whether high accuracy is enabled.
442 *
443 * Since: 2.26
444 */
445gboolean webkit_geolocation_manager_get_enable_high_accuracy(WebKitGeolocationManager* manager)
446{
447 g_return_val_if_fail(WEBKIT_IS_GEOLOCATION_MANAGER(manager), FALSE);
448
449 return manager->priv->highAccuracyEnabled;
450}
451