diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.cpp b/Userland/Libraries/LibWeb/HTML/Navigable.cpp
index e29aeaa842..a742d4caec 100644
--- a/Userland/Libraries/LibWeb/HTML/Navigable.cpp
+++ b/Userland/Libraries/LibWeb/HTML/Navigable.cpp
@@ -930,7 +930,8 @@ WebIDL::ExceptionOr Navigable::navigate(
target_step = traversable->current_session_history_step();
}
- // FIXME: 10. Apply the history step targetStep to traversable.
+ // 10. Apply the history step targetStep to traversable.
+ traversable->apply_the_history_step(target_step);
}).release_value_but_fixme_should_propagate_errors();
});
diff --git a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp
index b648577a34..ae7ecd3ff9 100644
--- a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp
+++ b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp
@@ -8,8 +8,10 @@
#include
#include
#include
+#include
#include
#include
+#include
namespace Web::HTML {
@@ -222,6 +224,221 @@ Vector> TraversableNavigable::get_all_navigables_whose_cur
return results;
}
+// https://html.spec.whatwg.org/multipage/browsing-the-web.html#apply-the-history-step
+void TraversableNavigable::apply_the_history_step(int step, Optional source_snapshot_params)
+{
+ // FIXME: 1. Assert: This is running within traversable's session history traversal queue.
+
+ // 2. Let targetStep be the result of getting the used step given traversable and step.
+ auto target_step = get_the_used_step(step);
+
+ // FIXME: 3. If initiatorToCheck is given, then:
+
+ // FIXME: 4. Let navigablesCrossingDocuments be the result of getting all navigables that might experience a cross-document traversal given traversable and targetStep.
+
+ // FIXME: 5. If checkForUserCancelation is true, and the result of checking if unloading is user-canceled given navigablesCrossingDocuments given traversable and targetStep is true, then return.
+
+ // 6. Let changingNavigables be the result of get all navigables whose current session history entry will change or reload given traversable and targetStep.
+ auto changing_navigables = get_all_navigables_whose_current_session_history_entry_will_change_or_reload(target_step);
+
+ // FIXME: 7. Let nonchangingNavigablesThatStillNeedUpdates be the result of getting all navigables that only need history object length/index update given traversable and targetStep.
+
+ // 8. For each navigable of changingNavigables:
+ for (auto& navigable : changing_navigables) {
+ // 1. Let targetEntry be the result of getting the target history entry given navigable and targetStep.
+ auto target_entry = navigable->get_the_target_history_entry(target_step);
+
+ // 2. Set navigable's current session history entry to targetEntry.
+ navigable->set_current_session_history_entry(target_entry);
+
+ // 3. Set navigable's ongoing navigation to "traversal".
+ m_ongoing_navigation = Traversal::Tag;
+ }
+
+ // 9. Let totalChangeJobs be the size of changingNavigables.
+ auto total_change_jobs = changing_navigables.size();
+
+ // 10. Let completedChangeJobs be 0.
+ size_t completed_change_jobs = 0;
+
+ struct ChangingNavigableContinuationState {
+ JS::Handle displayed_document;
+ JS::Handle target_entry;
+ JS::Handle navigable;
+ bool update_only;
+ };
+
+ // 11. Let changingNavigableContinuations be an empty queue of changing navigable continuation states.
+ Queue changing_navigable_continuations;
+
+ // 12. For each navigable of changingNavigables, queue a global task on the navigation and traversal task source of navigable's active window to run the steps:
+ for (auto& navigable : changing_navigables) {
+ queue_global_task(Task::Source::NavigationAndTraversal, *navigable->active_window(), [&] {
+ // 1. Let displayedEntry be navigable's active session history entry.
+ auto displayed_entry = navigable->active_session_history_entry();
+
+ // 2. Let targetEntry be navigable's current session history entry.
+ auto target_entry = navigable->current_session_history_entry();
+
+ // 3. Let changingNavigableContinuation be a changing navigable continuation state with:
+ auto changing_navigable_continuation = ChangingNavigableContinuationState {
+ .displayed_document = displayed_entry->document_state->document(),
+ .target_entry = target_entry,
+ .navigable = navigable,
+ .update_only = false
+ };
+
+ // 4. If displayedEntry is targetEntry and targetEntry's document state's reload pending is false, then:
+ if (displayed_entry == target_entry && !target_entry->document_state->reload_pending()) {
+ // 1. Set changingNavigableContinuation's update-only to true.
+ changing_navigable_continuation.update_only = true;
+
+ // 2. Enqueue changingNavigableContinuation on changingNavigableContinuations.
+ changing_navigable_continuations.enqueue(move(changing_navigable_continuation));
+
+ // 3. Abort these steps.
+ return;
+ }
+
+ // 5. Let oldOrigin be targetEntry's document state's origin.
+ [[maybe_unused]] auto old_origin = target_entry->document_state->origin();
+
+ auto after_document_populated = [target_entry, changing_navigable_continuation, &changing_navigable_continuations]() mutable {
+ // 1. If targetEntry's document is null, then set changingNavigableContinuation's update-only to true.
+ if (!target_entry->document_state->document()) {
+ changing_navigable_continuation.update_only = true;
+ }
+
+ // FIXME: 2. If targetEntry's document's origin is not oldOrigin, then set targetEntry's serialized state to StructuredSerializeForStorage(null).
+
+ // FIXME: 3. If all of the following are true:
+
+ // 4. Enqueue changingNavigableContinuation on changingNavigableContinuations.
+ changing_navigable_continuations.enqueue(move(changing_navigable_continuation));
+ };
+
+ // 6. If targetEntry's document is null, or targetEntry's document state's reload pending is true, then:
+ if (!target_entry->document_state->document() || target_entry->document_state->reload_pending()) {
+ // FIXME: 1. Let navTimingType be "back_forward" if targetEntry's document is null; otherwise "reload".
+
+ // FIXME: 2. Let targetSnapshotParams be the result of snapshotting target snapshot params given navigable.
+
+ // 3. Let potentiallyTargetSpecificSourceSnapshotParams be sourceSnapshotParams.
+ Optional potentially_target_specific_source_snapshot_params = source_snapshot_params;
+
+ // FIXME: 4. If potentiallyTargetSpecificSourceSnapshotParams is null, then set it to the result of snapshotting source snapshot params given navigable's active document.
+ if (!potentially_target_specific_source_snapshot_params.has_value()) {
+ potentially_target_specific_source_snapshot_params = SourceSnapshotParams {
+ .has_transient_activation = false,
+ .sandboxing_flags = navigable->active_document()->active_sandboxing_flag_set(),
+ .allows_downloading = true,
+ .fetch_client = navigable->active_document()->relevant_settings_object(),
+ .source_policy_container = navigable->active_document()->policy_container()
+ };
+ }
+
+ // 5. Set targetEntry's document state's reload pending to false.
+ target_entry->document_state->set_reload_pending(false);
+
+ // FIXME: 6. Let allowPOST be targetEntry's document state's reload pending.
+
+ // 7. In parallel, attempt to populate the history entry's document for targetEntry, given navigable, potentiallyTargetSpecificSourceSnapshotParams,
+ // targetSnapshotParams, with allowPOST set to allowPOST and completionSteps set to queue a global task on the navigation and traversal task source given
+ // navigable's active window to run afterDocumentPopulated.
+ navigable->populate_session_history_entry_document(target_entry, {}, {}, *potentially_target_specific_source_snapshot_params, [this, after_document_populated]() mutable {
+ queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), [after_document_populated]() mutable {
+ after_document_populated();
+ });
+ })
+ .release_value_but_fixme_should_propagate_errors();
+ }
+ // Otherwise, run afterDocumentPopulated immediately.
+ else {
+ after_document_populated();
+ }
+ });
+ }
+
+ // FIXME: 13. Let navigablesThatMustWaitBeforeHandlingSyncNavigation be an empty set.
+
+ // FIXME: 14. While completedChangeJobs does not equal totalChangeJobs:
+ while (completed_change_jobs != total_change_jobs) {
+ // FIXME: 1. If traversable's running nested apply history step is false, then:
+
+ // AD-HOC: Since currently populate_session_history_entry_document does not run in parallel
+ // we call spin_until to interrupt execution of this function and let document population
+ // to complete.
+ Platform::EventLoopPlugin::the().spin_until([&] {
+ return !changing_navigable_continuations.is_empty() || completed_change_jobs == total_change_jobs;
+ });
+
+ if (changing_navigable_continuations.is_empty()) {
+ continue;
+ }
+
+ // 2. Let changingNavigableContinuation be the result of dequeuing from changingNavigableContinuations.
+ auto changing_navigable_continuation = changing_navigable_continuations.dequeue();
+
+ // 3. If changingNavigableContinuation is nothing, then continue.
+
+ // 4. Let displayedDocument be changingNavigableContinuation's displayed document.
+ auto displayed_document = changing_navigable_continuation.displayed_document;
+
+ // 5. Let targetEntry be changingNavigableContinuation's target entry.
+ auto target_entry = changing_navigable_continuation.target_entry;
+
+ // 6. Let navigable be changingNavigableContinuation's navigable.
+ auto navigable = changing_navigable_continuation.navigable;
+
+ // 7. Set navigable's ongoing navigation to null.
+ m_ongoing_navigation = {};
+
+ // 8. Let (scriptHistoryLength, scriptHistoryIndex) be the result of getting the history object length and index given traversable and targetStep.
+ auto [script_history_length, script_history_index] = get_the_history_object_length_and_index(target_step);
+ (void)script_history_length;
+ (void)script_history_index;
+
+ // FIXME: 9. Append navigable to navigablesThatMustWaitBeforeHandlingSyncNavigation.
+
+ // 10. Queue a global task on the navigation and traversal task source given navigable's active window to run the steps:
+ queue_global_task(Task::Source::NavigationAndTraversal, *navigable->active_window(), [&, target_entry, navigable, displayed_document, update_only = changing_navigable_continuation.update_only] {
+ // 1. If changingNavigableContinuation's update-only is false, then:
+ if (!update_only) {
+ // 1. Unload displayedDocument given targetEntry's document.
+ displayed_document->unload(target_entry->document_state->document());
+
+ // FIXME: 2. For each childNavigable of displayedDocument's descendant navigables, queue a global task on the navigation and traversal task source given
+ // childNavigable's active window to unload childNavigable's active document.
+
+ // 3. Activate history entry targetEntry for navigable.
+ navigable->activate_history_entry(*target_entry);
+ }
+
+ // FIXME: 2. If targetEntry's document is not equal to displayedDocument, then queue a global task on the navigation and traversal task source given targetEntry's document's
+ // relevant global object to perform the following step. Otherwise, continue onward to perform the following step within the currently-queued task.
+
+ // FIXME: 3. Update document for history step application given targetEntry's document, targetEntry, changingNavigableContinuation's update-only, scriptHistoryLength, and
+ // scriptHistoryIndex.
+
+ // 4. Increment completedChangeJobs.
+ completed_change_jobs++;
+ });
+ }
+
+ // FIXME: 15. Let totalNonchangingJobs be the size of nonchangingNavigablesThatStillNeedUpdates.
+
+ // FIXME: 16. Let completedNonchangingJobs be 0.
+
+ // FIXME: 17. Let (scriptHistoryLength, scriptHistoryIndex) be the result of getting the history object length and index given traversable and targetStep.
+
+ // FIXME: 18. For each navigable of nonchangingNavigablesThatStillNeedUpdates, queue a global task on the navigation and traversal task source given navigable's active window to run the steps:
+
+ // FIXME: 19. Wait for completedNonchangingJobs to equal totalNonchangingJobs.
+
+ // 20. Set traversable's current session history step to targetStep.
+ m_current_session_history_step = target_step;
+}
+
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#clear-the-forward-session-history
void TraversableNavigable::clear_the_forward_session_history()
{
diff --git a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.h b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.h
index 66fa313a18..68be095642 100644
--- a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.h
+++ b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.h
@@ -36,6 +36,8 @@ public:
};
HistoryObjectLengthAndIndex get_the_history_object_length_and_index(int) const;
+ void apply_the_history_step(int step, Optional = {});
+
int get_the_used_step(int step) const;
Vector> get_all_navigables_whose_current_session_history_entry_will_change_or_reload(int) const;
Vector get_all_used_history_steps() const;