diff --git a/Libraries/LibWeb/Geolocation/Geolocation.cpp b/Libraries/LibWeb/Geolocation/Geolocation.cpp index ed4aeea3842..ad4d23696ef 100644 --- a/Libraries/LibWeb/Geolocation/Geolocation.cpp +++ b/Libraries/LibWeb/Geolocation/Geolocation.cpp @@ -4,12 +4,27 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include 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 success_callback, - [[maybe_unused]] GC::Ptr error_callback, [[maybe_unused]] Optional 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 success_callback, + GC::Ptr error_callback, PositionOptions options) +{ + // 1. If this's relevant global object's associated Document is not fully active: + auto& window = as(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 success_callback, - [[maybe_unused]] GC::Ptr error_callback, [[maybe_unused]] Optional options) +WebIDL::Long Geolocation::watch_position(GC::Ref success_callback, + GC::Ptr 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::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 success_callback, + GC::Ptr error_callback, PositionOptions options, Optional 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()) { + // 1. If emulatedPositionData is a GeolocationPositionError: + if (emulated_position_data.has()) { + // 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()); + + // 2. Terminate this algorithm. + return; + } + + // 2. Let position be a new GeolocationPosition passing emulatedPositionData, acquisitionTime and + // options.enableHighAccuracy. + auto position = realm().create(realm(), + emulated_position_data.get>(), + 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 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 position_data = realm().create(realm()); + + // 2. Set position to a new GeolocationPosition passing positionData, acquisitionTime and + // options.enableHighAccuracy. + position = realm().create(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 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(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::relevant_global_object(*this)).navigable(); + + // 2. If navigable is null, return null. + if (!navigable) + return Empty {}; + + // 3. Let traversable be navigable’s 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 success_callback, + GC::Ptr error_callback, PositionOptions options, Optional 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::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> 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(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(); } } diff --git a/Libraries/LibWeb/Geolocation/Geolocation.h b/Libraries/LibWeb/Geolocation/Geolocation.h index e8c764662b7..0f302b3753a 100644 --- a/Libraries/LibWeb/Geolocation/Geolocation.h +++ b/Libraries/LibWeb/Geolocation/Geolocation.h @@ -7,6 +7,8 @@ #pragma once #include +#include +#include #include 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, 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, GC::Ptr, Optional); - WebIDL::Long watch_position(GC::Ref, GC::Ptr, Optional); + void get_current_position(GC::Ref, GC::Ptr, PositionOptions); + WebIDL::Long watch_position(GC::Ref, GC::Ptr, 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, GC::Ptr, PositionOptions, Optional); + void call_back_with_error(GC::Ptr, GeolocationPositionError::ErrorCode) const; + EmulatedPositionData get_emulated_position_data() const; + void request_a_position(GC::Ref, GC::Ptr, PositionOptions, Optional = {}); + void run_in_parallel_when_document_is_visible(DOM::Document&, GC::Ref>); + + // https://w3c.github.io/geolocation/#dfn-watchids + HashTable m_watch_ids; + + // https://w3c.github.io/geolocation/#dfn-cachedposition + GC::Ptr m_cached_position; + + Vector> m_timeout_timers; }; } diff --git a/Libraries/LibWeb/Geolocation/GeolocationCoordinates.cpp b/Libraries/LibWeb/Geolocation/GeolocationCoordinates.cpp index 1f4e4ff58f7..b609cb3b2c7 100644 --- a/Libraries/LibWeb/Geolocation/GeolocationCoordinates.cpp +++ b/Libraries/LibWeb/Geolocation/GeolocationCoordinates.cpp @@ -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); diff --git a/Libraries/LibWeb/Geolocation/GeolocationCoordinates.h b/Libraries/LibWeb/Geolocation/GeolocationCoordinates.h index a9b0e51b78d..f1fa14e3cec 100644 --- a/Libraries/LibWeb/Geolocation/GeolocationCoordinates.h +++ b/Libraries/LibWeb/Geolocation/GeolocationCoordinates.h @@ -10,32 +10,37 @@ namespace Web::Geolocation { +struct CoordinatesData { + double accuracy { 0.0 }; + double latitude { 0.0 }; + double longitude { 0.0 }; + Optional altitude; + Optional altitude_accuracy; + Optional heading; + Optional 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 altitude() const { return m_altitude; } - Optional altitude_accuracy() const { return m_altitude_accuracy; } - Optional heading() const { return m_heading; } - Optional 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 altitude() const { return m_coordinates_data.altitude; } + Optional altitude_accuracy() const { return m_coordinates_data.altitude_accuracy; } + Optional heading() const { return m_coordinates_data.heading; } + Optional 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 m_altitude; - Optional m_altitude_accuracy; - Optional m_heading; - Optional m_speed; + CoordinatesData m_coordinates_data; }; } diff --git a/Libraries/LibWeb/Geolocation/GeolocationPosition.cpp b/Libraries/LibWeb/Geolocation/GeolocationPosition.cpp index f54536af53b..410d40e67bd 100644 --- a/Libraries/LibWeb/Geolocation/GeolocationPosition.cpp +++ b/Libraries/LibWeb/Geolocation/GeolocationPosition.cpp @@ -12,10 +12,12 @@ namespace Web::Geolocation { GC_DEFINE_ALLOCATOR(GeolocationPosition); -GeolocationPosition::GeolocationPosition(JS::Realm& realm, GC::Ref coords, HighResolutionTime::EpochTimeStamp timestamp) +GeolocationPosition::GeolocationPosition(JS::Realm& realm, GC::Ref coords, + HighResolutionTime::EpochTimeStamp timestamp, bool is_high_accuracy) : PlatformObject(realm) , m_coords(coords) , m_timestamp(timestamp) + , m_is_high_accuracy(is_high_accuracy) { } diff --git a/Libraries/LibWeb/Geolocation/GeolocationPosition.h b/Libraries/LibWeb/Geolocation/GeolocationPosition.h index 583dd35c3c6..5b8cf5c8ab0 100644 --- a/Libraries/LibWeb/Geolocation/GeolocationPosition.h +++ b/Libraries/LibWeb/Geolocation/GeolocationPosition.h @@ -20,15 +20,19 @@ class GeolocationPosition : public Bindings::PlatformObject { public: GC::Ref 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, HighResolutionTime::EpochTimeStamp); + GeolocationPosition(JS::Realm&, GC::Ref, HighResolutionTime::EpochTimeStamp, bool is_high_accuracy); virtual void initialize(JS::Realm&) override; virtual void visit_edges(Visitor&) override; GC::Ref m_coords; HighResolutionTime::EpochTimeStamp m_timestamp; + + // https://w3c.github.io/geolocation/#dfn-ishighaccuracy + bool m_is_high_accuracy; }; } diff --git a/Libraries/LibWeb/Geolocation/GeolocationPositionError.cpp b/Libraries/LibWeb/Geolocation/GeolocationPositionError.cpp index ebd76d83e3c..4b12505e6bd 100644 --- a/Libraries/LibWeb/Geolocation/GeolocationPositionError.cpp +++ b/Libraries/LibWeb/Geolocation/GeolocationPositionError.cpp @@ -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(); +} + } diff --git a/Libraries/LibWeb/Geolocation/GeolocationPositionError.h b/Libraries/LibWeb/Geolocation/GeolocationPositionError.h index fe39fc32575..3edeb67d34a 100644 --- a/Libraries/LibWeb/Geolocation/GeolocationPositionError.h +++ b/Libraries/LibWeb/Geolocation/GeolocationPositionError.h @@ -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; }; } diff --git a/Libraries/LibWeb/HTML/EventLoop/Task.h b/Libraries/LibWeb/HTML/EventLoop/Task.h index 8d61fbf2dbd..7775fd9c18f 100644 --- a/Libraries/LibWeb/HTML/EventLoop/Task.h +++ b/Libraries/LibWeb/HTML/EventLoop/Task.h @@ -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, diff --git a/Libraries/LibWeb/HTML/TraversableNavigable.cpp b/Libraries/LibWeb/HTML/TraversableNavigable.cpp index db000cacf2d..0ff3627e4f8 100644 --- a/Libraries/LibWeb/HTML/TraversableNavigable.cpp +++ b/Libraries/LibWeb/HTML/TraversableNavigable.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +75,8 @@ TraversableNavigable::~TraversableNavigable() = default; void TraversableNavigable::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); + if (m_emulated_position_data.has>()) + visitor.visit(m_emulated_position_data.get>()); visitor.visit(m_session_history_entries); visitor.visit(m_session_history_traversal_queue); visitor.visit(m_storage_shed); @@ -164,6 +167,24 @@ WebIDL::ExceptionOr> 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( + 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 + * Copyright (c) 2025, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -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); @@ -173,6 +179,9 @@ private: RefPtr 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 {