diff --git a/Userland/Libraries/LibWeb/HTML/Navigation.cpp b/Userland/Libraries/LibWeb/HTML/Navigation.cpp
index a4aaea60c3..4c07411e99 100644
--- a/Userland/Libraries/LibWeb/HTML/Navigation.cpp
+++ b/Userland/Libraries/LibWeb/HTML/Navigation.cpp
@@ -16,6 +16,7 @@
#include
#include
#include
+#include
#include
namespace Web::HTML {
@@ -328,6 +329,63 @@ WebIDL::ExceptionOr Navigation::reload(NavigationReloadOptions
return navigation_api_method_tracker_derived_result(api_method_tracker);
}
+// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-traverseto
+WebIDL::ExceptionOr Navigation::traverse_to(String key, NavigationOptions const& options)
+{
+ auto& realm = this->realm();
+ // The traverseTo(key, options) method steps are:
+
+ // 1. If this's current entry index is −1, then return an early error result for an "InvalidStateError" DOMException.
+ if (m_current_entry_index == -1)
+ return early_error_result(WebIDL::InvalidStateError::create(realm, "Cannot traverseTo: no current session history entry"));
+
+ // 2. If this's entry list does not contain a NavigationHistoryEntry whose session history entry's navigation API key equals key,
+ // then return an early error result for an "InvalidStateError" DOMException.
+ auto it = m_entry_list.find_if([&key](auto const& entry) {
+ return entry->session_history_entry().navigation_api_key == key;
+ });
+ if (it == m_entry_list.end())
+ return early_error_result(WebIDL::InvalidStateError::create(realm, "Cannot traverseTo: key not found in session history list"));
+
+ // 3. Return the result of performing a navigation API traversal given this, key, and options.
+ return perform_a_navigation_api_traversal(key, options);
+}
+
+// https://html.spec.whatwg.org/multipage/nav-history-apis.html#performing-a-navigation-api-traversal
+WebIDL::ExceptionOr Navigation::back(NavigationOptions const& options)
+{
+ auto& realm = this->realm();
+ // The back(options) method steps are:
+
+ // 1. If this's current entry index is −1 or 0, then return an early error result for an "InvalidStateError" DOMException.
+ if (m_current_entry_index == -1 || m_current_entry_index == 0)
+ return early_error_result(WebIDL::InvalidStateError::create(realm, "Cannot navigate back: no previous session history entry"));
+
+ // 2. Let key be this's entry list[this's current entry index − 1]'s session history entry's navigation API key.
+ auto key = m_entry_list[m_current_entry_index - 1]->session_history_entry().navigation_api_key;
+
+ // 3. Return the result of performing a navigation API traversal given this, key, and options.
+ return perform_a_navigation_api_traversal(key, options);
+}
+
+// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-forward
+WebIDL::ExceptionOr Navigation::forward(NavigationOptions const& options)
+{
+ auto& realm = this->realm();
+ // The forward(options) method steps are:
+
+ // 1. If this's current entry index is −1 or is equal to this's entry list's size − 1,
+ // then return an early error result for an "InvalidStateError" DOMException.
+ if (m_current_entry_index == -1 || m_current_entry_index == static_cast(m_entry_list.size() - 1))
+ return early_error_result(WebIDL::InvalidStateError::create(realm, "Cannot navigate forward: no next session history entry"));
+
+ // 2. Let key be this's entry list[this's current entry index + 1]'s session history entry's navigation API key.
+ auto key = m_entry_list[m_current_entry_index + 1]->session_history_entry().navigation_api_key;
+
+ // 3. Return the result of performing a navigation API traversal given this, key, and options.
+ return perform_a_navigation_api_traversal(key, options);
+}
+
void Navigation::set_onnavigate(WebIDL::CallbackType* event_handler)
{
set_event_handler_attribute(HTML::EventNames::navigate, event_handler);
@@ -496,4 +554,148 @@ JS::NonnullGCPtr Navigation::maybe_set_the_upcoming_
return api_method_tracker;
}
+// https://html.spec.whatwg.org/multipage/nav-history-apis.html#add-an-upcoming-traverse-api-method-tracker
+JS::NonnullGCPtr Navigation::add_an_upcoming_traverse_api_method_tracker(String destination_key, JS::Value info)
+{
+ auto& vm = this->vm();
+ auto& realm = relevant_realm(*this);
+ // To add an upcoming traverse API method tracker given a Navigation navigation, a string destinationKey, and a JavaScript value info:
+
+ // 1. Let committedPromise and finishedPromise be new promises created in navigation's relevant realm.
+ auto committed_promise = WebIDL::create_promise(realm);
+ auto finished_promise = WebIDL::create_promise(realm);
+
+ // 2. Mark as handled finishedPromise.
+ // NOTE: See the previous discussion about why this is done
+ // https://html.spec.whatwg.org/multipage/nav-history-apis.html#note-mark-as-handled-navigation-api-finished
+ WebIDL::mark_promise_as_handled(*finished_promise);
+
+ // 3. Let apiMethodTracker be a new navigation API method tracker with:
+ // navigation object: navigation
+ // key: destinationKey
+ // info: info
+ // serialized state: null
+ // comitted-to entry: null
+ // comitted promise: committedPromise
+ // finished promise: finishedPromise
+ auto api_method_tracker = vm.heap().allocate_without_realm(
+ /* .navigation = */ *this,
+ /* .key = */ destination_key,
+ /* .info = */ info,
+ /* .serialized_state = */ OptionalNone {},
+ /* .commited_to_entry = */ nullptr,
+ /* .committed_promise = */ committed_promise,
+ /* .finished_promise = */ finished_promise);
+
+ // 4. Set navigation's upcoming traverse API method trackers[key] to apiMethodTracker.
+ // FIXME: Fix spec typo key --> destinationKey
+ m_upcoming_traverse_api_method_trackers.set(destination_key, api_method_tracker);
+
+ // 5. Return apiMethodTracker.
+ return api_method_tracker;
+}
+
+// https://html.spec.whatwg.org/multipage/nav-history-apis.html#performing-a-navigation-api-traversal
+WebIDL::ExceptionOr Navigation::perform_a_navigation_api_traversal(String key, NavigationOptions const& options)
+{
+ auto& realm = this->realm();
+ // To perform a navigation API traversal given a Navigation navigation, a string key, and a NavigationOptions options:
+
+ // 1. Let document be this's relevant global object's associated Document.
+ auto& document = verify_cast(relevant_global_object(*this)).associated_document();
+
+ // 2. If document is not fully active, then return an early error result for an "InvalidStateError" DOMException.
+ if (!document.is_fully_active())
+ return early_error_result(WebIDL::InvalidStateError::create(realm, "Document is not fully active"));
+
+ // 3. If document's unload counter is greater than 0, then return an early error result for an "InvalidStateError" DOMException.
+ if (document.unload_counter() > 0)
+ return early_error_result(WebIDL::InvalidStateError::create(realm, "Document already unloaded"));
+
+ // 4. Let current be the current entry of navigation.
+ auto current = current_entry();
+
+ // 5. If key equals current's session history entry's navigation API key, then return
+ // «[ "committed" → a promise resolved with current, "finished" → a promise resolved with current ]».
+ if (key == current->session_history_entry().navigation_api_key) {
+ return NavigationResult {
+ .committed = WebIDL::create_resolved_promise(realm, current)->promise(),
+ .finished = WebIDL::create_resolved_promise(realm, current)->promise()
+ };
+ }
+
+ // 6. If navigation's upcoming traverse API method trackers[key] exists,
+ // then return a navigation API method tracker-derived result for navigation's upcoming traverse API method trackers[key].
+ if (auto maybe_tracker = m_upcoming_traverse_api_method_trackers.get(key); maybe_tracker.has_value())
+ return navigation_api_method_tracker_derived_result(maybe_tracker.value());
+
+ // 7. Let info be options["info"], if it exists; otherwise, undefined.
+ auto info = options.info.value_or(JS::js_undefined());
+
+ // 8. Let apiMethodTracker be the result of adding an upcoming traverse API method tracker for navigation given key and info.
+ auto api_method_tracker = add_an_upcoming_traverse_api_method_tracker(key, info);
+
+ // 9. Let navigable be document's node navigable.
+ auto navigable = document.navigable();
+
+ // 10. Let traversable be navigable's traversable navigable.
+ auto traversable = navigable->traversable_navigable();
+
+ // 11. Let sourceSnapshotParams be the result of snapshotting source snapshot params given document.
+ auto source_snapshot_params = document.snapshot_source_snapshot_params();
+
+ // 12. Append the following session history traversal steps to traversable:
+ traversable->append_session_history_traversal_steps([key, api_method_tracker, navigable, source_snapshot_params, this] {
+ // 1. Let navigableSHEs be the result of getting session history entries given navigable.
+ auto navigable_shes = navigable->get_session_history_entries();
+
+ // 2. Let targetSHE be the session history entry in navigableSHEs whose navigation API key is key. If no such entry exists, then:
+ auto it = navigable_shes.find_if([&key](auto const& entry) {
+ return entry->navigation_api_key == key;
+ });
+ if (it == navigable_shes.end()) {
+ // NOTE: This path is taken if navigation's entry list was outdated compared to navigableSHEs,
+ // which can occur for brief periods while all the relevant threads and processes are being synchronized in reaction to a history change.
+
+ // 1. Queue a global task on the navigation and traversal task source given navigation's relevant global object
+ // to reject the finished promise for apiMethodTracker with an "InvalidStateError" DOMException.
+ queue_global_task(HTML::Task::Source::NavigationAndTraversal, relevant_global_object(*this), [this, api_method_tracker] {
+ auto& reject_realm = relevant_realm(*this);
+ WebIDL::reject_promise(reject_realm, api_method_tracker->finished_promise,
+ WebIDL::InvalidStateError::create(reject_realm, "Cannot traverse with stale session history entry"));
+ });
+
+ // 2. Abort these steps.
+ return;
+ }
+ auto target_she = *it;
+
+ // 3. If targetSHE is navigable's active session history entry, then abort these steps.
+ // NOTE: This can occur if a previously queued traversal already took us to this session history entry.
+ // In that case the previous traversal will have dealt with apiMethodTracker already.
+ if (target_she == navigable->active_session_history_entry())
+ return;
+
+ // FIXME: 4. Let result be the result of applying the traverse history step given by targetSHE's step to traversable,
+ // given sourceSnapshotParams, navigable, and "none".
+ (void)source_snapshot_params;
+
+ // NOTE: When result is "canceled-by-beforeunload" or "initiator-disallowed", the navigate event was never fired,
+ // aborting the ongoing navigation would not be correct; it would result in a navigateerror event without a
+ // preceding navigate event. In the "canceled-by-navigate" case, navigate is fired, but the inner navigate event
+ // firing algorithm will take care of aborting the ongoing navigation.
+
+ // FIXME: 5. If result is "canceled-by-beforeunload", then queue a global task on the navigation and traversal task source
+ // given navigation's relevant global object to reject the finished promise for apiMethodTracker with a
+ // new "AbortError"DOMException created in navigation's relevant realm.
+
+ // FIXME: 6. If result is "initiator-disallowed", then queue a global task on the navigation and traversal task source
+ // given navigation's relevant global object to reject the finished promise for apiMethodTracker with a
+ // new "SecurityError" DOMException created in navigation's relevant realm.
+ });
+
+ // 13. Return a navigation API method tracker-derived result for apiMethodTracker.
+ return navigation_api_method_tracker_derived_result(api_method_tracker);
+}
+
}
diff --git a/Userland/Libraries/LibWeb/HTML/Navigation.h b/Userland/Libraries/LibWeb/HTML/Navigation.h
index 508ff44c42..57a90f49d0 100644
--- a/Userland/Libraries/LibWeb/HTML/Navigation.h
+++ b/Userland/Libraries/LibWeb/HTML/Navigation.h
@@ -85,6 +85,10 @@ public:
WebIDL::ExceptionOr navigate(String url, NavigationNavigateOptions const&);
WebIDL::ExceptionOr reload(NavigationReloadOptions const&);
+ WebIDL::ExceptionOr traverse_to(String key, NavigationOptions const&);
+ WebIDL::ExceptionOr back(NavigationOptions const&);
+ WebIDL::ExceptionOr forward(NavigationOptions const&);
+
// Event Handlers
void set_onnavigate(WebIDL::CallbackType*);
WebIDL::CallbackType* onnavigate();
@@ -114,6 +118,8 @@ private:
NavigationResult early_error_result(AnyException);
JS::NonnullGCPtr maybe_set_the_upcoming_non_traverse_api_method_tracker(JS::Value info, Optional);
+ JS::NonnullGCPtr add_an_upcoming_traverse_api_method_tracker(String destination_key, JS::Value info);
+ WebIDL::ExceptionOr perform_a_navigation_api_traversal(String key, NavigationOptions const&);
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-entry-list
// Each Navigation has an associated entry list, a list of NavigationHistoryEntry objects, initially empty.
diff --git a/Userland/Libraries/LibWeb/HTML/Navigation.idl b/Userland/Libraries/LibWeb/HTML/Navigation.idl
index 3b3b7e1076..0b7fba4767 100644
--- a/Userland/Libraries/LibWeb/HTML/Navigation.idl
+++ b/Userland/Libraries/LibWeb/HTML/Navigation.idl
@@ -17,9 +17,9 @@ interface Navigation : EventTarget {
NavigationResult navigate(USVString url, optional NavigationNavigateOptions options = {});
NavigationResult reload(optional NavigationReloadOptions options = {});
- // NavigationResult traverseTo(DOMString key, optional NavigationOptions options = {});
- // NavigationResult back(optional NavigationOptions options = {});
- // NavigationResult forward(optional NavigationOptions options = {});
+ NavigationResult traverseTo(DOMString key, optional NavigationOptions options = {});
+ NavigationResult back(optional NavigationOptions options = {});
+ NavigationResult forward(optional NavigationOptions options = {});
attribute EventHandler onnavigate;
attribute EventHandler onnavigatesuccess;