LibWeb: Implement emulated Geolocation position retrieval
Some checks are pending
CI / macOS, arm64, Sanitizer_CI, Clang (push) Waiting to run
CI / Linux, x86_64, Fuzzers_CI, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer_CI, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer_CI, Clang (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run

This implements enough of the Geolocation spec that it is now possible
for websites to retrieve the current geo position or try to watch for
updates (which currently never happen).

As it stands now, it only returns a single emulated position that points
to San Francisco.
This commit is contained in:
Jelle Raaijmakers 2025-06-22 20:26:03 +02:00 committed by Jelle Raaijmakers
commit 71f03cb785
Notes: github-actions[bot] 2025-06-24 09:35:05 +00:00
11 changed files with 477 additions and 48 deletions

View file

@ -4,12 +4,27 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Time.h>
#include <LibWeb/Bindings/GeolocationPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/DocumentObserver.h>
#include <LibWeb/Geolocation/Geolocation.h>
#include <LibWeb/Geolocation/GeolocationPosition.h>
#include <LibWeb/HTML/EventLoop/Task.h>
#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HTML/TraversableNavigable.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
#include <LibWeb/Platform/Timer.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
namespace Web::Geolocation {
static constexpr u32 VISIBILITY_STATE_TIMEOUT_MS = 5'000;
static WebIDL::UnsignedLong s_next_watch_id = 0;
GC_DEFINE_ALLOCATOR(Geolocation);
Geolocation::Geolocation(JS::Realm& realm)
@ -23,48 +38,364 @@ void Geolocation::initialize(JS::Realm& realm)
Base::initialize(realm);
}
// https://w3c.github.io/geolocation/#dom-geolocation-getcurrentposition
void Geolocation::get_current_position([[maybe_unused]] GC::Ref<WebIDL::CallbackType> success_callback,
[[maybe_unused]] GC::Ptr<WebIDL::CallbackType> error_callback, [[maybe_unused]] Optional<PositionOptions> options)
void Geolocation::visit_edges(Visitor& visitor)
{
// FIXME: 1. If this's relevant global object's associated Document is not fully active:
{
// FIXME: 1. Call back with error errorCallback and POSITION_UNAVAILABLE.
Base::visit_edges(visitor);
visitor.visit(m_cached_position);
visitor.visit(m_timeout_timers);
}
// FIXME: 2. Terminate this algorithm.
// https://w3c.github.io/geolocation/#dom-geolocation-getcurrentposition
void Geolocation::get_current_position(GC::Ref<WebIDL::CallbackType> success_callback,
GC::Ptr<WebIDL::CallbackType> error_callback, PositionOptions options)
{
// 1. If this's relevant global object's associated Document is not fully active:
auto& window = as<HTML::Window>(HTML::relevant_global_object(*this));
if (!window.associated_document().is_fully_active()) {
// 1. Call back with error errorCallback and POSITION_UNAVAILABLE.
call_back_with_error(error_callback, GeolocationPositionError::ErrorCode::PositionUnavailable);
// 2. Terminate this algorithm.
return;
}
// FIXME: 2. Request a position passing this, successCallback, errorCallback, and options.
dbgln("FIXME: Geolocation::get_current_position() not implemented yet");
// 2. Request a position passing this, successCallback, errorCallback, and options.
request_a_position(success_callback, error_callback, options);
}
// https://w3c.github.io/geolocation/#watchposition-method
WebIDL::Long Geolocation::watch_position([[maybe_unused]] GC::Ref<WebIDL::CallbackType> success_callback,
[[maybe_unused]] GC::Ptr<WebIDL::CallbackType> error_callback, [[maybe_unused]] Optional<PositionOptions> options)
WebIDL::Long Geolocation::watch_position(GC::Ref<WebIDL::CallbackType> success_callback,
GC::Ptr<WebIDL::CallbackType> error_callback, PositionOptions options)
{
// FIXME: 1. If this's relevant global object's associated Document is not fully active:
{
// FIXME: 1. Call back with error passing errorCallback and POSITION_UNAVAILABLE.
// 1. If this's relevant global object's associated Document is not fully active:
auto& window = as<HTML::Window>(HTML::relevant_global_object(*this));
if (!window.associated_document().is_fully_active()) {
// 1. Call back with error passing errorCallback and POSITION_UNAVAILABLE.
call_back_with_error(error_callback, GeolocationPositionError::ErrorCode::PositionUnavailable);
// FIXME: 2. Return 0.
// 2. Return 0.
return 0;
}
// FIXME: 2. Let watchId be an implementation-defined unsigned long that is greater than zero.
// 2. Let watchId be an implementation-defined unsigned long that is greater than zero.
auto watch_id = ++s_next_watch_id;
// FIXME: 3. Append watchId to this's [[watchIDs]].
// 3. Append watchId to this's [[watchIDs]].
m_watch_ids.set(watch_id);
// FIXME: 4. Request a position passing this, successCallback, errorCallback, options, and watchId.
// 4. Request a position passing this, successCallback, errorCallback, options, and watchId.
request_a_position(success_callback, error_callback, options, watch_id);
// FIXME: 5. Return watchId.
dbgln("FIXME: Geolocation::watch_position() not implemented yet");
return 0;
// 5. Return watchId.
return watch_id;
}
// https://w3c.github.io/geolocation/#clearwatch-method
void Geolocation::clear_watch([[maybe_unused]] WebIDL::Long watch_id)
void Geolocation::clear_watch(WebIDL::Long watch_id)
{
// FIXME: 1. Remove watchId from this's [[watchIDs]].
dbgln("FIXME: Geolocation::clear_watch() not implemented yet");
// 1. Remove watchId from this's [[watchIDs]].
m_watch_ids.remove(watch_id);
}
// https://w3c.github.io/geolocation/#dfn-acquire-a-position
void Geolocation::acquire_a_position(GC::Ref<WebIDL::CallbackType> success_callback,
GC::Ptr<WebIDL::CallbackType> error_callback, PositionOptions options, Optional<WebIDL::UnsignedLong> watch_id)
{
// 1. If watchId was passed and this's [[watchIDs]] does not contain watchId, terminate this algorithm.
if (watch_id.has_value() && !m_watch_ids.contains(watch_id.value()))
return;
// 2. Let acquisitionTime be a new EpochTimeStamp that represents now.
HighResolutionTime::EpochTimeStamp const acquisition_time = AK::UnixDateTime::now().milliseconds_since_epoch();
// 3. Let timeoutTime be the sum of acquisitionTime and options.timeout.
[[maybe_unused]] HighResolutionTime::EpochTimeStamp const timeout_time = acquisition_time + options.timeout;
// 4. Let cachedPosition be this's [[cachedPosition]].
auto cached_position = m_cached_position;
// FIXME: 5. Create an implementation-specific timeout task that elapses at timeoutTime, during which it tries to acquire
// the device's position by running the following steps:
{
// FIXME: 1. Let permission be get the current permission state of "geolocation".
// FIXME: 2. If permission is "denied":
if (false) {
// FIXME: 1. Stop timeout.
// FIXME: 2. Do the user or system denied permission failure case step.
}
// FIXME: 3. If permission is "granted":
if (true) {
// 1. Check if an emulated position should be used by running the following steps:
{
// 1. Let emulatedPositionData be get emulated position data passing this.
auto emulated_position_data = get_emulated_position_data();
// 2. If emulatedPositionData is not null:
if (!emulated_position_data.has<Empty>()) {
// 1. If emulatedPositionData is a GeolocationPositionError:
if (emulated_position_data.has<GeolocationPositionError::ErrorCode>()) {
// 1. Call back with error passing errorCallback and emulatedPositionData.
// FIXME: We pass along the code instead of the entire error object. Spec issue:
// https://github.com/w3c/geolocation/issues/186
call_back_with_error(error_callback, emulated_position_data.get<GeolocationPositionError::ErrorCode>());
// 2. Terminate this algorithm.
return;
}
// 2. Let position be a new GeolocationPosition passing emulatedPositionData, acquisitionTime and
// options.enableHighAccuracy.
auto position = realm().create<GeolocationPosition>(realm(),
emulated_position_data.get<GC::Ref<GeolocationCoordinates>>(),
acquisition_time,
options.enable_high_accuracy);
// 3. Queue a task on the geolocation task source with a step that invokes successCallback with
// « position » and "report".
HTML::queue_a_task(HTML::Task::Source::Geolocation, nullptr, nullptr, GC::create_function(heap(), [success_callback, position] {
(void)WebIDL::invoke_callback(success_callback, {}, WebIDL::ExceptionBehavior::Report, { { position } });
}));
// 4. Terminate this algorithm.
return;
}
}
// 2. Let position be null.
GC::Ptr<GeolocationPosition> position;
// 3. If cachedPosition is not null, and options.maximumAge is greater than 0:
if (cached_position && options.maximum_age > 0) {
// 1. Let cacheTime be acquisitionTime minus the value of the options.maximumAge member.
HighResolutionTime::EpochTimeStamp const cache_time = acquisition_time - options.maximum_age;
// 2. If cachedPosition's timestamp's value is greater than cacheTime, and
// cachedPosition.[[isHighAccuracy]] equals options.enableHighAccuracy:
if (cached_position->timestamp() > cache_time
&& cached_position->is_high_accuracy() == options.enable_high_accuracy) {
// 1. Queue a task on the geolocation task source with a step that invokes successCallback with
// « cachedPosition » and "report".
HTML::queue_a_task(HTML::Task::Source::Geolocation, nullptr, nullptr, GC::create_function(heap(), [success_callback, cached_position] {
(void)WebIDL::invoke_callback(success_callback, {}, WebIDL::ExceptionBehavior::Report, { { cached_position } });
}));
// 2. Terminate this algorithm.
return;
}
}
// FIXME: 4. Otherwise, if position is not cachedPosition, try to acquire position data from the underlying system,
// optionally taking into consideration the value of options.enableHighAccuracy during acquisition.
// FIXME: 5. If the timeout elapses during acquisition, or acquiring the device's position results in failure:
if (false) {
// FIXME: 1. Stop the timeout.
// FIXME: 2. Go to dealing with failures.
// 3. Terminate this algorithm.
return;
}
// FIXME: 6. If acquiring the position data from the system succeeds:
if (true) {
// FIXME: 1. Let positionData be a map with the following name/value pairs based on the acquired position data:
// * longitude:
// A double that represents the longitude coordinates on the Earth's surface in degrees, using
// the [WGS84] coordinate system. Longitude measures how far east or west a point is from the
// Prime Meridian.
// * altitude:
// A double? that represents the altitude in meters above the [WGS84] ellipsoid, or null if not
// available. Altitude measures the height above sea level.
// * accuracy:
// A non-negative double that represents the accuracy value indicating the 95% confidence level
// in meters. Accuracy measures how close the measured coordinates are to the true position.
// * altitudeAccuracy:
// A non-negative double? that represents the altitude accuracy, or null if not available,
// indicating the 95% confidence level in meters. Altitude accuracy measures how close the
// measured altitude is to the true altitude.
// * speed:
// A non-negative double? that represents the speed in meters per second, or null if not
// available. Speed measures how fast the device is moving.
// * heading:
// A double? that represents the heading in degrees, or null if not available or the device is
// stationary. Heading measures the direction in which the device is moving relative to true
// north.
GC::Ref<GeolocationCoordinates> position_data = realm().create<GeolocationCoordinates>(realm());
// 2. Set position to a new GeolocationPosition passing positionData, acquisitionTime and
// options.enableHighAccuracy.
position = realm().create<GeolocationPosition>(realm(), position_data, acquisition_time, options.enable_high_accuracy);
// 3. Set this's [[cachedPosition]] to position.
m_cached_position = *position;
}
// FIXME: 7. Stop the timeout.
// 8. Queue a task on the geolocation task source with a step that invokes successCallback with « position »
// and "report".
HTML::queue_a_task(HTML::Task::Source::Geolocation, nullptr, nullptr, GC::create_function(heap(), [success_callback, position] {
(void)WebIDL::invoke_callback(success_callback, {}, WebIDL::ExceptionBehavior::Report, { { position } });
}));
}
}
}
// https://w3c.github.io/geolocation/#dfn-call-back-with-error
void Geolocation::call_back_with_error(GC::Ptr<WebIDL::CallbackType> callback, GeolocationPositionError::ErrorCode code) const
{
// 1. If callback is null, return.
if (!callback)
return;
// 2. Let error be a newly created GeolocationPositionError instance whose code attribute is initialized to code.
auto error = realm().create<GeolocationPositionError>(realm(), code);
// 3. Queue a task on the geolocation task source with a step that invokes callback with « error » and "report".
HTML::queue_a_task(HTML::Task::Source::Geolocation, nullptr, nullptr, GC::create_function(heap(), [callback, error] {
(void)WebIDL::invoke_callback(*callback, {}, WebIDL::ExceptionBehavior::Report,
{ { error } });
}));
}
// https://w3c.github.io/geolocation/#dfn-get-emulated-position-data
EmulatedPositionData Geolocation::get_emulated_position_data() const
{
// 1. Let navigable be geolocation's relevant global object's associated Document's node navigable.
auto navigable = as<HTML::Window>(HTML::relevant_global_object(*this)).navigable();
// 2. If navigable is null, return null.
if (!navigable)
return Empty {};
// 3. Let traversable be navigables top-level traversable.
auto traversable = navigable->top_level_traversable();
// 4. If traversable is null, return null.
if (!traversable)
return Empty {};
// 5. Return traversable's associated emulated position data.
return traversable->emulated_position_data();
}
// https://w3c.github.io/geolocation/#dfn-request-a-position
void Geolocation::request_a_position(GC::Ref<WebIDL::CallbackType> success_callback,
GC::Ptr<WebIDL::CallbackType> error_callback, PositionOptions options, Optional<WebIDL::UnsignedLong> watch_id)
{
// 1. Let watchIDs be geolocation's [[watchIDs]].
// 2. Let document be the geolocation's relevant global object's associated Document.
[[maybe_unused]] auto& document = as<HTML::Window>(HTML::relevant_global_object(*this)).associated_document();
// FIXME: 3. If document is not allowed to use the "geolocation" feature:
if (false) {
// 1. If watchId was passed, remove watchId from watchIDs.
if (watch_id.has_value())
m_watch_ids.remove(watch_id.value());
// 2. Call back with error passing errorCallback and PERMISSION_DENIED.
call_back_with_error(error_callback, GeolocationPositionError::ErrorCode::PermissionDenied);
// 3. Terminate this algorithm.
return;
}
// 4. If geolocation's environment settings object is a non-secure context:
if (is_non_secure_context(HTML::relevant_settings_object(*this))) {
// 1. If watchId was passed, remove watchId from watchIDs.
if (watch_id.has_value())
m_watch_ids.remove(watch_id.value());
// 2. Call back with error passing errorCallback and PERMISSION_DENIED.
call_back_with_error(error_callback, GeolocationPositionError::ErrorCode::PermissionDenied);
// 3. Terminate this algorithm.
return;
}
// 5. If document's visibility state is "hidden", wait for the following page visibility change steps to run:
run_in_parallel_when_document_is_visible(document, GC::create_function(heap(), [this, watch_id, success_callback, error_callback, options] {
// 1. Assert: document's visibility state is "visible".
// 2. Continue to the next steps below.
// AD-HOC: This is implemented by run_in_parallel_when_document_is_visible().
// FIXME: 6. Let descriptor be a new PermissionDescriptor whose name is "geolocation".
// 7. In parallel:
// AD-HOC: run_in_parallel_when_document_is_visible() already runs this in parallel.
{
// FIXME: 1. Set permission to request permission to use descriptor.
// FIXME: 2. If permission is "denied", then:
if (false) {
// 1. If watchId was passed, remove watchId from watchIDs.
if (watch_id.has_value())
m_watch_ids.remove(watch_id.value());
// 2. Call back with error passing errorCallback and PERMISSION_DENIED.
call_back_with_error(error_callback, GeolocationPositionError::ErrorCode::PermissionDenied);
// 3. Terminate this algorithm.
return;
}
// FIXME: 3. Wait to acquire a position passing successCallback, errorCallback, options, and watchId.
acquire_a_position(success_callback, error_callback, options, watch_id);
// 4. If watchId was not passed, terminate this algorithm.
if (!watch_id.has_value())
return;
// FIXME: 5. While watchIDs contains watchId:
{
// FIXME: 1. Wait for a significant change of geographic position. What constitutes a significant change of
// geographic position is left to the implementation. User agents MAY impose a rate limit on how
// frequently position changes are reported. User agents MUST consider invoking set emulated position
// data as a significant change.
// FIXME: 2. If document is not fully active or visibility state is not "visible", go back to the previous step
// and again wait for a significant change of geographic position.
// FIXME: 3. Wait to acquire a position passing successCallback, errorCallback, options, and watchId.
}
}
}));
}
void Geolocation::run_in_parallel_when_document_is_visible(DOM::Document& document, GC::Ref<GC::Function<void()>> callback)
{
// Run callback in parallel if the document is already visible.
if (document.visibility_state_value() == HTML::VisibilityState::Visible) {
Platform::EventLoopPlugin::the().deferred_invoke(callback);
return;
}
// Run the callback as soon as the document becomes visible. If we time out, do not run the callback at all.
auto document_observer = realm().create<DOM::DocumentObserver>(realm(), document);
auto timeout_timer = Platform::Timer::create_single_shot(heap(), VISIBILITY_STATE_TIMEOUT_MS, {});
m_timeout_timers.append(timeout_timer);
auto clear_observer_and_timer = [this, document_observer, timeout_timer] {
document_observer->set_document_visibility_state_observer({});
timeout_timer->stop();
m_timeout_timers.remove_first_matching([&](auto timer) { return timer == timeout_timer; });
};
timeout_timer->on_timeout = GC::create_function(heap(), [clear_observer_and_timer] {
dbgln("Geolocation: Waiting for visibility state update timed out");
clear_observer_and_timer();
});
document_observer->set_document_visibility_state_observer([clear_observer_and_timer, callback](HTML::VisibilityState state) {
if (state == HTML::VisibilityState::Visible) {
clear_observer_and_timer();
callback->function()();
}
});
timeout_timer->start();
}
}

View file

@ -7,6 +7,8 @@
#pragma once
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Geolocation/GeolocationPositionError.h>
#include <LibWeb/Platform/Timer.h>
#include <LibWeb/WebIDL/Types.h>
namespace Web::Geolocation {
@ -17,20 +19,39 @@ struct PositionOptions {
WebIDL::UnsignedLong timeout { 0xFFFFFFFF };
WebIDL::UnsignedLong maximum_age { 0 };
};
// https://w3c.github.io/geolocation/#dfn-emulated-position-data
using EmulatedPositionData = Variant<Empty, GC::Ref<GeolocationCoordinates>, GeolocationPositionError::ErrorCode>;
// https://w3c.github.io/geolocation/#geolocation_interface
class Geolocation : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(Geolocation, Bindings::PlatformObject);
GC_DECLARE_ALLOCATOR(Geolocation);
public:
void get_current_position(GC::Ref<WebIDL::CallbackType>, GC::Ptr<WebIDL::CallbackType>, Optional<PositionOptions>);
WebIDL::Long watch_position(GC::Ref<WebIDL::CallbackType>, GC::Ptr<WebIDL::CallbackType>, Optional<PositionOptions>);
void get_current_position(GC::Ref<WebIDL::CallbackType>, GC::Ptr<WebIDL::CallbackType>, PositionOptions);
WebIDL::Long watch_position(GC::Ref<WebIDL::CallbackType>, GC::Ptr<WebIDL::CallbackType>, PositionOptions);
void clear_watch(WebIDL::Long);
private:
Geolocation(JS::Realm&);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Visitor&) override;
void acquire_a_position(GC::Ref<WebIDL::CallbackType>, GC::Ptr<WebIDL::CallbackType>, PositionOptions, Optional<WebIDL::UnsignedLong>);
void call_back_with_error(GC::Ptr<WebIDL::CallbackType>, GeolocationPositionError::ErrorCode) const;
EmulatedPositionData get_emulated_position_data() const;
void request_a_position(GC::Ref<WebIDL::CallbackType>, GC::Ptr<WebIDL::CallbackType>, PositionOptions, Optional<WebIDL::UnsignedLong> = {});
void run_in_parallel_when_document_is_visible(DOM::Document&, GC::Ref<GC::Function<void()>>);
// https://w3c.github.io/geolocation/#dfn-watchids
HashTable<WebIDL::UnsignedLong> m_watch_ids;
// https://w3c.github.io/geolocation/#dfn-cachedposition
GC::Ptr<GeolocationPosition> m_cached_position;
Vector<GC::Ref<Platform::Timer>> m_timeout_timers;
};
}

View file

@ -17,6 +17,12 @@ GeolocationCoordinates::GeolocationCoordinates(JS::Realm& realm)
{
}
GeolocationCoordinates::GeolocationCoordinates(JS::Realm& realm, CoordinatesData data)
: PlatformObject(realm)
, m_coordinates_data(move(data))
{
}
void GeolocationCoordinates::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(GeolocationCoordinates);

View file

@ -10,32 +10,37 @@
namespace Web::Geolocation {
struct CoordinatesData {
double accuracy { 0.0 };
double latitude { 0.0 };
double longitude { 0.0 };
Optional<double> altitude;
Optional<double> altitude_accuracy;
Optional<double> heading;
Optional<double> speed;
};
// https://w3c.github.io/geolocation/#coordinates_interface
class GeolocationCoordinates : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(GeolocationCoordinates, Bindings::PlatformObject);
GC_DECLARE_ALLOCATOR(GeolocationCoordinates);
public:
double accuracy() const { return m_accuracy; }
double latitude() const { return m_latitude; }
double longitude() const { return m_longitude; }
Optional<double> altitude() const { return m_altitude; }
Optional<double> altitude_accuracy() const { return m_altitude_accuracy; }
Optional<double> heading() const { return m_heading; }
Optional<double> speed() const { return m_speed; }
double accuracy() const { return m_coordinates_data.accuracy; }
double latitude() const { return m_coordinates_data.latitude; }
double longitude() const { return m_coordinates_data.longitude; }
Optional<double> altitude() const { return m_coordinates_data.altitude; }
Optional<double> altitude_accuracy() const { return m_coordinates_data.altitude_accuracy; }
Optional<double> heading() const { return m_coordinates_data.heading; }
Optional<double> speed() const { return m_coordinates_data.speed; }
private:
GeolocationCoordinates(JS::Realm&);
explicit GeolocationCoordinates(JS::Realm&);
GeolocationCoordinates(JS::Realm&, CoordinatesData);
virtual void initialize(JS::Realm&) override;
double m_accuracy { 0.0 };
double m_latitude { 0.0 };
double m_longitude { 0.0 };
Optional<double> m_altitude;
Optional<double> m_altitude_accuracy;
Optional<double> m_heading;
Optional<double> m_speed;
CoordinatesData m_coordinates_data;
};
}

View file

@ -12,10 +12,12 @@ namespace Web::Geolocation {
GC_DEFINE_ALLOCATOR(GeolocationPosition);
GeolocationPosition::GeolocationPosition(JS::Realm& realm, GC::Ref<GeolocationCoordinates> coords, HighResolutionTime::EpochTimeStamp timestamp)
GeolocationPosition::GeolocationPosition(JS::Realm& realm, GC::Ref<GeolocationCoordinates> coords,
HighResolutionTime::EpochTimeStamp timestamp, bool is_high_accuracy)
: PlatformObject(realm)
, m_coords(coords)
, m_timestamp(timestamp)
, m_is_high_accuracy(is_high_accuracy)
{
}

View file

@ -20,15 +20,19 @@ class GeolocationPosition : public Bindings::PlatformObject {
public:
GC::Ref<GeolocationCoordinates const> coords() const { return m_coords; }
HighResolutionTime::EpochTimeStamp timestamp() const { return m_timestamp; }
bool is_high_accuracy() const { return m_is_high_accuracy; }
private:
GeolocationPosition(JS::Realm&, GC::Ref<GeolocationCoordinates>, HighResolutionTime::EpochTimeStamp);
GeolocationPosition(JS::Realm&, GC::Ref<GeolocationCoordinates>, HighResolutionTime::EpochTimeStamp, bool is_high_accuracy);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Visitor&) override;
GC::Ref<GeolocationCoordinates const> m_coords;
HighResolutionTime::EpochTimeStamp m_timestamp;
// https://w3c.github.io/geolocation/#dfn-ishighaccuracy
bool m_is_high_accuracy;
};
}

View file

@ -12,10 +12,9 @@ namespace Web::Geolocation {
GC_DEFINE_ALLOCATOR(GeolocationPositionError);
GeolocationPositionError::GeolocationPositionError(JS::Realm& realm, ErrorCode code, String message)
GeolocationPositionError::GeolocationPositionError(JS::Realm& realm, ErrorCode code)
: PlatformObject(realm)
, m_code(code)
, m_message(move(message))
{
}
@ -25,4 +24,19 @@ void GeolocationPositionError::initialize(JS::Realm& realm)
Base::initialize(realm);
}
// https://w3c.github.io/geolocation/#message-attribute
String GeolocationPositionError::message() const
{
// The message attribute is a developer-friendly textual description of the code attribute.
switch (m_code) {
case ErrorCode::PositionUnavailable:
return "Position unavailable"_string;
case ErrorCode::PermissionDenied:
return "Permission denied"_string;
case ErrorCode::Timeout:
return "Timeout"_string;
}
VERIFY_NOT_REACHED();
}
}

View file

@ -24,15 +24,14 @@ public:
};
ErrorCode code() const { return m_code; }
String message() const { return m_message; }
String message() const;
private:
GeolocationPositionError(JS::Realm&, ErrorCode, String);
GeolocationPositionError(JS::Realm&, ErrorCode);
virtual void initialize(JS::Realm&) override;
ErrorCode m_code { 0 };
String m_message;
};
}

View file

@ -35,6 +35,9 @@ public:
TimerTask,
JavaScriptEngine,
// https://w3c.github.io/geolocation/#dfn-geolocation-task-source
Geolocation,
// https://html.spec.whatwg.org/multipage/webappapis.html#navigation-and-traversal-task-source
NavigationAndTraversal,

View file

@ -9,6 +9,7 @@
#include <LibGfx/SkiaBackendContext.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/Geolocation/GeolocationCoordinates.h>
#include <LibWeb/HTML/BrowsingContextGroup.h>
#include <LibWeb/HTML/DocumentState.h>
#include <LibWeb/HTML/Navigation.h>
@ -74,6 +75,8 @@ TraversableNavigable::~TraversableNavigable() = default;
void TraversableNavigable::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
if (m_emulated_position_data.has<GC::Ref<Geolocation::GeolocationCoordinates>>())
visitor.visit(m_emulated_position_data.get<GC::Ref<Geolocation::GeolocationCoordinates>>());
visitor.visit(m_session_history_entries);
visitor.visit(m_session_history_traversal_queue);
visitor.visit(m_storage_shed);
@ -164,6 +167,24 @@ WebIDL::ExceptionOr<GC::Ref<TraversableNavigable>> TraversableNavigable::create_
auto traversable = TRY(create_a_new_top_level_traversable(page, nullptr, {}));
page->set_top_level_traversable(traversable);
// AD-HOC: Set the default top-level emulated position data for the traversable, which points to Market St. SF.
// FIXME: We should not emulate by default, but ask the user what to do. E.g. disable Geolocation, set an emulated
// position, or allow Ladybird to engage with the system's geolocation services. This is completely separate
// from the permission model for "powerful features" such as Geolocation.
auto& realm = traversable->active_document()->realm();
auto emulated_position_coordinates = realm.create<Geolocation::GeolocationCoordinates>(
realm,
Geolocation::CoordinatesData {
.accuracy = 100.0,
.latitude = 37.7647658,
.longitude = -122.4345892,
.altitude = 60.0,
.altitude_accuracy = 10.0,
.heading = 0.0,
.speed = 0.0,
});
traversable->set_emulated_position_data(emulated_position_coordinates);
// AD-HOC: Mark the about:blank document as finished parsing if we're only going to about:blank
// Skip the initial navigation as well. This matches the behavior of the window open steps.
@ -1434,4 +1455,18 @@ void TraversableNavigable::start_display_list_rendering(NonnullRefPtr<Painting::
m_rendering_thread.enqueue_rendering_task(move(display_list), move(scroll_state_snapshot), move(backing_store), move(callback));
}
// https://w3c.github.io/geolocation/#dfn-emulated-position-data
Geolocation::EmulatedPositionData const& TraversableNavigable::emulated_position_data() const
{
VERIFY(is_top_level_traversable());
return m_emulated_position_data;
}
// https://w3c.github.io/geolocation/#dfn-emulated-position-data
void TraversableNavigable::set_emulated_position_data(Geolocation::EmulatedPositionData data)
{
VERIFY(is_top_level_traversable());
m_emulated_position_data = data;
}
}

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,6 +8,7 @@
#pragma once
#include <AK/Vector.h>
#include <LibWeb/Geolocation/Geolocation.h>
#include <LibWeb/HTML/Navigable.h>
#include <LibWeb/HTML/NavigationType.h>
#include <LibWeb/HTML/RenderingThread.h>
@ -120,6 +122,10 @@ public:
bool needs_repaint() const { return m_needs_repaint; }
void set_needs_repaint() { m_needs_repaint = true; }
// https://w3c.github.io/geolocation/#dfn-emulated-position-data
Geolocation::EmulatedPositionData const& emulated_position_data() const;
void set_emulated_position_data(Geolocation::EmulatedPositionData data);
private:
TraversableNavigable(GC::Ref<Page>);
@ -173,6 +179,9 @@ private:
RefPtr<Gfx::SkiaBackendContext> m_skia_backend_context;
bool m_needs_repaint { true };
// https://w3c.github.io/geolocation/#dfn-emulated-position-data
Geolocation::EmulatedPositionData m_emulated_position_data;
};
struct BrowsingContextAndDocument {