From f296382e1a504de3e34d38b06ea7028dfc7ece78 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Wed, 20 Sep 2023 23:21:16 -0600 Subject: [PATCH] LibWeb: Implement inform the navigation api about aborting navigation This also requires implementing the abort the ongoing navigation AO on Navigation, which will be used from other NavigateEvent AOs. --- Userland/Libraries/LibWeb/HTML/Navigable.cpp | 22 ++++++- Userland/Libraries/LibWeb/HTML/Navigable.h | 2 + .../Libraries/LibWeb/HTML/NavigateEvent.h | 2 + Userland/Libraries/LibWeb/HTML/Navigation.cpp | 64 +++++++++++++++++++ Userland/Libraries/LibWeb/HTML/Navigation.h | 4 ++ 5 files changed, 93 insertions(+), 1 deletion(-) diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.cpp b/Userland/Libraries/LibWeb/HTML/Navigable.cpp index d4079c2262..3593084464 100644 --- a/Userland/Libraries/LibWeb/HTML/Navigable.cpp +++ b/Userland/Libraries/LibWeb/HTML/Navigable.cpp @@ -286,7 +286,8 @@ void Navigable::set_ongoing_navigation(Variant ongoing if (m_ongoing_navigation == ongoing_navigation) return; - // FIXME: 2. Inform the navigation API about aborting navigation given navigable. + // 2. Inform the navigation API about aborting navigation given navigable. + inform_the_navigation_api_about_aborting_navigation(); // 3. Set navigable's ongoing navigation to newValue. m_ongoing_navigation = ongoing_navigation; @@ -1671,4 +1672,23 @@ bool Navigable::has_a_rendering_opportunity() const return true; } +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#inform-the-navigation-api-about-aborting-navigation +void Navigable::inform_the_navigation_api_about_aborting_navigation() +{ + // FIXME: 1. If this algorithm is running on navigable's active window's relevant agent's event loop, then continue on to the following steps. + // Otherwise, queue a global task on the navigation and traversal task source given navigable's active window to run the following steps. + + queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), [this] { + // 2. Let navigation be navigable's active window's navigation API. + auto navigation = active_window()->navigation(); + + // 3. If navigation's ongoing navigate event is null, then return. + if (navigation->ongoing_navigate_event() == nullptr) + return; + + // 4. Abort the ongoing navigation given navigation. + navigation->abort_the_ongoing_navigation(); + }); +} + } diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.h b/Userland/Libraries/LibWeb/HTML/Navigable.h index 69f4b287a4..b89fc3db48 100644 --- a/Userland/Libraries/LibWeb/HTML/Navigable.h +++ b/Userland/Libraries/LibWeb/HTML/Navigable.h @@ -171,6 +171,8 @@ private: void scroll_offset_did_change(); + void inform_the_navigation_api_about_aborting_navigation(); + // https://html.spec.whatwg.org/multipage/document-sequences.html#nav-id String m_id; diff --git a/Userland/Libraries/LibWeb/HTML/NavigateEvent.h b/Userland/Libraries/LibWeb/HTML/NavigateEvent.h index 4fea44ae51..211fdf0c22 100644 --- a/Userland/Libraries/LibWeb/HTML/NavigateEvent.h +++ b/Userland/Libraries/LibWeb/HTML/NavigateEvent.h @@ -62,6 +62,8 @@ public: virtual ~NavigateEvent() override; + JS::NonnullGCPtr abort_controller() const { return *m_abort_controller; } + private: NavigateEvent(JS::Realm&, FlyString const& event_name, NavigateEventInit const& event_init); diff --git a/Userland/Libraries/LibWeb/HTML/Navigation.cpp b/Userland/Libraries/LibWeb/HTML/Navigation.cpp index f88db5185e..b54d1af5fa 100644 --- a/Userland/Libraries/LibWeb/HTML/Navigation.cpp +++ b/Userland/Libraries/LibWeb/HTML/Navigation.cpp @@ -10,7 +10,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -702,4 +704,66 @@ WebIDL::ExceptionOr Navigation::perform_a_navigation_api_trave return navigation_api_method_tracker_derived_result(api_method_tracker); } +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#abort-the-ongoing-navigation +void Navigation::abort_the_ongoing_navigation(Optional> error) +{ + auto& realm = relevant_realm(*this); + + // To abort the ongoing navigation given a Navigation navigation and an optional DOMException error: + + // 1. Let event be navigation's ongoing navigate event. + auto event = ongoing_navigate_event(); + + // 2. Assert: event is not null. + VERIFY(event != nullptr); + + // 3. Set navigation's focus changed during ongoing navigation to false. + m_focus_changed_during_ongoing_navigation = false; + + // 4. Set navigation's suppress normal scroll restoration during ongoing navigation to false. + m_suppress_scroll_restoration_during_ongoing_navigation = false; + + // 5. If error was not given, then let error be a new "AbortError" DOMException created in navigation's relevant realm. + if (!error.has_value()) + error = WebIDL::AbortError::create(realm, "Navigation aborted"_fly_string); + + VERIFY(error.has_value()); + + // 6. If event's dispatch flag is set, then set event's canceled flag to true. + if (event->dispatched()) + event->set_cancelled(true); + + // 7. Signal abort on event's abort controller given error. + event->abort_controller()->abort(error.value()); + + // 8. Set navigation's ongoing navigate event to null. + m_ongoing_navigate_event = nullptr; + + // 9. Fire an event named navigateerror at navigation using ErrorEvent, with error initialized to error, + // and message, filename, lineno, and colno initialized to appropriate values that can be extracted + // from error and the current JavaScript stack in the same underspecified way that the report the exception algorithm does. + ErrorEventInit event_init = {}; + event_init.error = error.value(); + // FIXME: Extract information from the exception and the JS context in the wishy-washy way the spec says here. + event_init.filename = String {}; + event_init.colno = 0; + event_init.lineno = 0; + event_init.message = String {}; + + dispatch_event(ErrorEvent::create(realm, EventNames::navigateerror, event_init)); + + // 10. If navigation's ongoing API method tracker is non-null, then reject the finished promise for apiMethodTracker with error. + if (m_ongoing_api_method_tracker != nullptr) + WebIDL::reject_promise(realm, m_ongoing_api_method_tracker->finished_promise, error.value()); + + // 11. If navigation's transition is not null, then: + if (m_transition != nullptr) { + // 1. Reject navigation's transition's finished promise with error. + m_transition->finished()->reject(error.value()); + + // 2. Set navigation's transition to null. + m_transition = nullptr; + } +} + } diff --git a/Userland/Libraries/LibWeb/HTML/Navigation.h b/Userland/Libraries/LibWeb/HTML/Navigation.h index eef25af99f..05d34b0f7f 100644 --- a/Userland/Libraries/LibWeb/HTML/Navigation.h +++ b/Userland/Libraries/LibWeb/HTML/Navigation.h @@ -106,9 +106,13 @@ public: // Abstract Operations bool has_entries_and_events_disabled() const; i64 get_the_navigation_api_entry_index(SessionHistoryEntry const&) const; + void abort_the_ongoing_navigation(Optional> error = {}); virtual ~Navigation() override; + // Internal Getters + JS::GCPtr ongoing_navigate_event() const { return m_ongoing_navigate_event; } + private: explicit Navigation(JS::Realm&);