blob: d0cb4c0be6a6d390d67e93cf8d7d6ad5600b57dc [file] [log] [blame]
/*
* Copyright (C) 2019 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "WebKitGeolocationManager.h"
#include "APIGeolocationProvider.h"
#include "GeoclueGeolocationProvider.h"
#include "WebGeolocationPosition.h"
#include "WebKitGeolocationManagerPrivate.h"
#include <glib/gi18n-lib.h>
#include <wtf/WallTime.h>
#include <wtf/glib/WTFGType.h>
using namespace WebKit;
using namespace WebCore;
/**
* SECTION:WebKitGeolocationManager
* @short_description: WebKitGeolocationManager
* @title: Geolocation manager
* @see_also: #WebKitGeolocationPermissionRequest, #WebKitWebContext
*
* WebKitGeolocationManager provides API to get the geographical position of the user.
* Once a #WebKitGeolocationPermissionRequest is allowed, when WebKit needs to know the
* user location #WebKitGeolocationManager::start signal is emitted. If the signal is handled
* and returns %TRUE, the application is responsible for providing the position every time it's
* updated by calling webkit_geolocation_manager_update_position(). The signal #WebKitGeolocationManager::stop
* will be emitted when location updates are no longer needed.
*
* Since: 2.26
*/
enum {
PROP_0,
PROP_ENABLE_HIGH_ACCURACY
};
enum {
START,
STOP,
LAST_SIGNAL
};
struct _WebKitGeolocationPosition {
_WebKitGeolocationPosition() = default;
_WebKitGeolocationPosition(double latitude, double longitude, double accuracy)
{
position.timestamp = WallTime::now().secondsSinceEpoch().value();
position.latitude = latitude;
position.longitude = longitude;
position.accuracy = accuracy;
}
explicit _WebKitGeolocationPosition(GeolocationPositionData&& corePosition)
: position(WTFMove(corePosition))
{
}
explicit _WebKitGeolocationPosition(const GeolocationPositionData& other)
{
position = other;
}
GeolocationPositionData position;
};
/**
* WebKitGeolocationPosition:
*
* WebKitGeolocationPosition is an opaque struct used to provide position updates to a
* #WebKitGeolocationManager using webkit_geolocation_manager_update_position().
*
* Since: 2.26
*/
G_DEFINE_BOXED_TYPE(WebKitGeolocationPosition, webkit_geolocation_position, webkit_geolocation_position_copy, webkit_geolocation_position_free)
/**
* webkit_geolocation_position_new:
* @latitude: a valid latitude in degrees
* @longitude: a valid longitude in degrees
* @accuracy: accuracy of location in meters
*
* Create a new #WebKitGeolocationPosition
*
* Returns: (transfer full): a newly created #WebKitGeolocationPosition
*
* Since: 2.26
*/
WebKitGeolocationPosition* webkit_geolocation_position_new(double latitude, double longitude, double accuracy)
{
auto* position = static_cast<WebKitGeolocationPosition*>(fastMalloc(sizeof(WebKitGeolocationPosition)));
new (position) WebKitGeolocationPosition(latitude, longitude, accuracy);
return position;
}
/**
* webkit_geolocation_position_copy:
* @position: a #WebKitGeolocationPosition
*
* Make a copy of the #WebKitGeolocationPosition
*
* Returns: (transfer full): a copy of @position
*
* Since: 2.26
*/
WebKitGeolocationPosition* webkit_geolocation_position_copy(WebKitGeolocationPosition* position)
{
g_return_val_if_fail(position, nullptr);
auto* copy = static_cast<WebKitGeolocationPosition*>(fastMalloc(sizeof(WebKitGeolocationPosition)));
new (copy) WebKitGeolocationPosition(position->position);
return copy;
}
/**
* webkit_geolocation_position_free:
* @position: a #WebKitGeolocationPosition
*
* Free the #WebKitGeolocationPosition
*
* Since: 2.26
*/
void webkit_geolocation_position_free(WebKitGeolocationPosition* position)
{
g_return_if_fail(position);
position->~WebKitGeolocationPosition();
fastFree(position);
}
/**
* webkit_geolocation_position_set_timestamp:
* @position: a #WebKitGeolocationPosition
* @timestamp: timestamp in seconds since the epoch, or 0 to use current time
*
* Set the @position timestamp. By default it's the time when the @position was created.
*
* Since: 2.26
*/
void webkit_geolocation_position_set_timestamp(WebKitGeolocationPosition* position, guint64 timestamp)
{
g_return_if_fail(position);
position->position.timestamp = timestamp ? static_cast<double>(timestamp) : WallTime::now().secondsSinceEpoch().value();
}
/**
* webkit_geolocation_position_set_altitude:
* @position: a #WebKitGeolocationPosition
* @altitude: altitude in meters
*
* Set the @position altitude
*
* Since: 2.26
*/
void webkit_geolocation_position_set_altitude(WebKitGeolocationPosition* position, double altitude)
{
g_return_if_fail(position);
position->position.altitude = altitude;
}
/**
* webkit_geolocation_position_set_altitude_accuracy:
* @position: a #WebKitGeolocationPosition
* @altitude_accuracy: accuracy of position altitude in meters
*
* Set the accuracy of @position altitude
*
* Since: 2.26
*/
void webkit_geolocation_position_set_altitude_accuracy(WebKitGeolocationPosition* position, double altitudeAccuracy)
{
g_return_if_fail(position);
position->position.altitudeAccuracy = altitudeAccuracy;
}
/**
* webkit_geolocation_position_set_heading:
* @position: a #WebKitGeolocationPosition
* @heading: heading in degrees
*
* Set the @position heading, as a positive angle between the direction of movement and the North
* direction, in clockwise direction.
*
* Since: 2.26
*/
void webkit_geolocation_position_set_heading(WebKitGeolocationPosition* position, double heading)
{
g_return_if_fail(position);
position->position.heading = heading;
}
/**
* webkit_geolocation_position_set_speed:
* @position: a #WebKitGeolocationPosition
* @speed: speed in meters per second
*
* Set the @position speed
*
* Since: 2.26
*/
void webkit_geolocation_position_set_speed(WebKitGeolocationPosition* position, double speed)
{
g_return_if_fail(position);
position->position.speed = speed;
}
struct _WebKitGeolocationManagerPrivate {
RefPtr<WebGeolocationManagerProxy> manager;
bool highAccuracyEnabled;
std::unique_ptr<GeoclueGeolocationProvider> geoclueProvider;
};
static guint signals[LAST_SIGNAL] = { 0, };
WEBKIT_DEFINE_TYPE(WebKitGeolocationManager, webkit_geolocation_manager, G_TYPE_OBJECT)
static void webkitGeolocationManagerStart(WebKitGeolocationManager* manager)
{
gboolean returnValue;
g_signal_emit(manager, signals[START], 0, &returnValue);
if (returnValue) {
manager->priv->geoclueProvider = nullptr;
return;
}
if (!manager->priv->geoclueProvider) {
manager->priv->geoclueProvider = makeUnique<GeoclueGeolocationProvider>();
manager->priv->geoclueProvider->setEnableHighAccuracy(manager->priv->highAccuracyEnabled);
}
manager->priv->geoclueProvider->start([manager](GeolocationPositionData&& corePosition, Optional<CString> error) {
if (error) {
webkit_geolocation_manager_failed(manager, error->data());
return;
}
WebKitGeolocationPosition position(WTFMove(corePosition));
webkit_geolocation_manager_update_position(manager, &position);
});
}
static void webkitGeolocationManagerStop(WebKitGeolocationManager* manager)
{
g_signal_emit(manager, signals[STOP], 0, nullptr);
if (manager->priv->geoclueProvider)
manager->priv->geoclueProvider->stop();
}
static void webkitGeolocationManagerSetEnableHighAccuracy(WebKitGeolocationManager* manager, bool enabled)
{
if (manager->priv->highAccuracyEnabled == enabled)
return;
manager->priv->highAccuracyEnabled = enabled;
g_object_notify(G_OBJECT(manager), "enable-high-accuracy");
if (manager->priv->geoclueProvider)
manager->priv->geoclueProvider->setEnableHighAccuracy(enabled);
}
class GeolocationProvider final : public API::GeolocationProvider {
public:
explicit GeolocationProvider(WebKitGeolocationManager* manager)
: m_manager(manager)
{
}
private:
void startUpdating(WebGeolocationManagerProxy&) override
{
webkitGeolocationManagerStart(m_manager);
}
void stopUpdating(WebGeolocationManagerProxy&) override
{
webkitGeolocationManagerStop(m_manager);
}
void setEnableHighAccuracy(WebGeolocationManagerProxy&, bool enabled) override
{
webkitGeolocationManagerSetEnableHighAccuracy(m_manager, enabled);
}
WebKitGeolocationManager* m_manager;
};
WebKitGeolocationManager* webkitGeolocationManagerCreate(WebGeolocationManagerProxy* proxy)
{
auto* manager = WEBKIT_GEOLOCATION_MANAGER(g_object_new(WEBKIT_TYPE_GEOLOCATION_MANAGER, nullptr));
manager->priv->manager = proxy;
proxy->setProvider(makeUnique<GeolocationProvider>(manager));
return manager;
}
static void webkitGeolocationManagerGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec)
{
WebKitGeolocationManager* manager = WEBKIT_GEOLOCATION_MANAGER(object);
switch (propId) {
case PROP_ENABLE_HIGH_ACCURACY:
g_value_set_boolean(value, webkit_geolocation_manager_get_enable_high_accuracy(manager));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
}
}
static void webkit_geolocation_manager_class_init(WebKitGeolocationManagerClass* geolocationManagerClass)
{
GObjectClass* gObjectClass = G_OBJECT_CLASS(geolocationManagerClass);
gObjectClass->get_property = webkitGeolocationManagerGetProperty;
/**
* WebKitGeolocationManager:enable-high-accuracy:
*
* Whether high accuracy is enabled. This is a read-only property that will be
* set to %TRUE when a #WebKitGeolocationManager needs to get accurate position updates.
* You can connect to notify::enable-high-accuracy signal to monitor it.
*
* Since: 2.26
*/
g_object_class_install_property(
gObjectClass,
PROP_ENABLE_HIGH_ACCURACY,
g_param_spec_boolean(
"enable-high-accuracy",
_("Enable high accuracy"),
_("Whether high accuracy is enabled"),
FALSE,
WEBKIT_PARAM_READABLE));
/**
* WebKitGeolocationManager::start:
* @manager: the #WebKitGeolocationManager on which the signal is emitted
*
* The signal is emitted to notify that @manager needs to start receiving
* position updates. After this signal is emitted the user should provide
* the updates using webkit_geolocation_manager_update_position() every time
* the position changes, or use webkit_geolocation_manager_failed() in case
* it isn't possible to determine the current position.
*
* If the signal is not handled, WebKit will try to determine the position
* using GeoClue if available.
*
* Returns: %TRUE to stop other handlers from being invoked for the event.
* %FALSE to propagate the event further.
*
* Since: 2.26
*/
signals[START] = g_signal_new(
"start",
G_TYPE_FROM_CLASS(geolocationManagerClass),
G_SIGNAL_RUN_LAST,
0,
g_signal_accumulator_true_handled, nullptr,
g_cclosure_marshal_generic,
G_TYPE_BOOLEAN, 0);
/**
* WebKitGeolocationManager::stop:
* @manager: the #WebKitGeolocationManager on which the signal is emitted
*
* The signal is emitted to notify that @manager doesn't need to receive
* position updates anymore.
*
* Since: 2.26
*/
signals[STOP] = g_signal_new(
"stop",
G_TYPE_FROM_CLASS(geolocationManagerClass),
G_SIGNAL_RUN_LAST,
0,
nullptr, nullptr,
g_cclosure_marshal_generic,
G_TYPE_NONE, 0);
}
/**
* webkit_geolocation_manager_update_position:
* @manager: a #WebKitGeolocationManager
* @position: a #WebKitGeolocationPosition
*
* Notify @manager that position has been updated to @position.
*
* Since: 2.26
*/
void webkit_geolocation_manager_update_position(WebKitGeolocationManager* manager, WebKitGeolocationPosition* position)
{
g_return_if_fail(WEBKIT_IS_GEOLOCATION_MANAGER(manager));
g_return_if_fail(position);
auto corePosition = position->position;
auto wkPosition = WebGeolocationPosition::create(WTFMove(corePosition));
manager->priv->manager->providerDidChangePosition(wkPosition.ptr());
}
/**
* webkit_geolocation_manager_failed:
* @manager: a #WebKitGeolocationManager
* @error_message: the error message
*
* Notify @manager that determining the position failed.
*
* Since: 2.26
*/
void webkit_geolocation_manager_failed(WebKitGeolocationManager* manager, const char* errorMessage)
{
g_return_if_fail(WEBKIT_IS_GEOLOCATION_MANAGER(manager));
manager->priv->manager->providerDidFailToDeterminePosition(String::fromUTF8(errorMessage));
}
/**
* webkit_geolocation_manager_get_enable_high_accuracy:
* @manager: a #WebKitGeolocationManager
*
* Get whether high accuracy is enabled.
*
* Since: 2.26
*/
gboolean webkit_geolocation_manager_get_enable_high_accuracy(WebKitGeolocationManager* manager)
{
g_return_val_if_fail(WEBKIT_IS_GEOLOCATION_MANAGER(manager), FALSE);
return manager->priv->highAccuracyEnabled;
}