diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 2a10835859..d29353979c 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -3605,6 +3605,21 @@ Painting::ViewportPaintable* Document::paintable() return static_cast(Node::paintable()); } +// https://html.spec.whatwg.org/multipage/browsing-the-web.html#restore-the-history-object-state +void Document::restore_the_history_object_state(JS::NonnullGCPtr entry) +{ + // 1. Let targetRealm be document's relevant realm. + auto& target_realm = HTML::relevant_realm(*this); + + // 2. Let state be StructuredDeserialize(entry's classic history API state, targetRealm). If this throws an exception, catch it and let state be null. + // 3. Set document's history object's state to state. + auto state_or_error = HTML::structured_deserialize(target_realm.vm(), entry->classic_history_api_state, target_realm, {}); + if (state_or_error.is_error()) + m_history->set_state(JS::js_null()); + else + m_history->set_state(state_or_error.release_value()); +} + // https://html.spec.whatwg.org/multipage/browsing-the-web.html#update-document-for-history-step-application void Document::update_for_history_step_application(JS::NonnullGCPtr entry, bool do_not_reactive, size_t script_history_length, size_t script_history_index) { diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h index 4a19cb4c82..79d1b6ddfd 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.h +++ b/Userland/Libraries/LibWeb/DOM/Document.h @@ -538,6 +538,7 @@ public: void update_for_history_step_application(JS::NonnullGCPtr, bool do_not_reactive, size_t script_history_length, size_t script_history_index); HashMap>& shared_image_requests(); + void restore_the_history_object_state(JS::NonnullGCPtr entry); JS::NonnullGCPtr timeline(); diff --git a/Userland/Libraries/LibWeb/HTML/Navigation.cpp b/Userland/Libraries/LibWeb/HTML/Navigation.cpp index 088b4f8cea..8dcab2d2f4 100644 --- a/Userland/Libraries/LibWeb/HTML/Navigation.cpp +++ b/Userland/Libraries/LibWeb/HTML/Navigation.cpp @@ -148,7 +148,7 @@ WebIDL::ExceptionOr Navigation::update_current_entry(NavigationUpdateCurre NavigationCurrentEntryChangeEventInit event_init = {}; event_init.navigation_type = {}; event_init.from = current; - dispatch_event(HTML::NavigationCurrentEntryChangeEvent::create(realm(), HTML::EventNames::currententrychange, event_init)); + dispatch_event(HTML::NavigationCurrentEntryChangeEvent::construct_impl(realm(), HTML::EventNames::currententrychange, event_init)); return {}; } @@ -868,6 +868,28 @@ void Navigation::reject_the_finished_promise(JS::NonnullGCPtr api_method_tracker, JS::NonnullGCPtr nhe) +{ + auto& realm = this->realm(); + + // 1. Set apiMethodTracker's committed-to entry to nhe. + api_method_tracker->commited_to_entry = nhe; + + // 2. If apiMethodTracker's serialized state is not null, then set nhe's session history entry's navigation API state to apiMethodTracker's serialized state.' + // NOTE: If it's null, then we're traversing to nhe via navigation.traverseTo(), which does not allow changing the state. + if (api_method_tracker->serialized_state.has_value()) { + // NOTE: At this point, apiMethodTracker's serialized state is no longer needed. + // Implementations might want to clear it out to avoid keeping it alive for the lifetime of the navigation API method tracker. + nhe->session_history_entry().navigation_api_state = *api_method_tracker->serialized_state; + api_method_tracker->serialized_state = {}; + } + + // 3. Resolve apiMethodTracker's committed promise with nhe. + TemporaryExecutionContext execution_context { relevant_settings_object(*this) }; + WebIDL::resolve_promise(realm, api_method_tracker->committed_promise, nhe); +} + // https://html.spec.whatwg.org/multipage/nav-history-apis.html#inner-navigate-event-firing-algorithm bool Navigation::inner_navigate_event_firing_algorithm( Bindings::NavigationType navigation_type, @@ -1327,4 +1349,134 @@ bool Navigation::fire_a_download_request_navigate_event(AK::URL destination_url, return inner_navigate_event_firing_algorithm(Bindings::NavigationType::Push, destination, user_involvement, {}, move(filename), {}); } +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#initialize-the-navigation-api-entries-for-a-new-document +void Navigation::initialize_the_navigation_api_entries_for_a_new_document(Vector> const& new_shes, JS::NonnullGCPtr initial_she) +{ + auto& realm = relevant_realm(*this); + + // 1. Assert: navigation's entry list is empty. + VERIFY(m_entry_list.is_empty()); + + // 2. Assert: navigation's current entry index is −1. + VERIFY(m_current_entry_index == -1); + + // 3. If navigation has entries and events disabled, then return. + if (has_entries_and_events_disabled()) + return; + + // 4. For each newSHE of newSHEs: + for (auto const& new_she : new_shes) { + // 1. Let newNHE be a new NavigationHistoryEntry created in the relevant realm of navigation. + // 2. Set newNHE's session history entry to newSHE. + auto new_nhe = NavigationHistoryEntry::create(realm, new_she); + + // 3. Append newNHE to navigation's entry list. + m_entry_list.append(new_nhe); + } + + // 5. Set navigation's current entry index to the result of getting the navigation API entry index of initialSHE within navigation. + m_current_entry_index = get_the_navigation_api_entry_index(*initial_she); +} + +// https://html.spec.whatwg.org/multipage/nav-history-apis.html#update-the-navigation-api-entries-for-a-same-document-navigation +void Navigation::update_the_navigation_api_entries_for_a_same_document_navigation(JS::NonnullGCPtr destination_she, Bindings::NavigationType navigation_type) +{ + auto& realm = relevant_realm(*this); + + // 1. If navigation has entries and events disabled, then return. + if (has_entries_and_events_disabled()) + return; + + // 2. Let oldCurrentNHE be the current entry of navigation. + auto old_current_nhe = current_entry(); + + // 3. Let disposedNHEs be a new empty list. + Vector> disposed_nhes; + + // 4. If navigationType is "traverse", then: + if (navigation_type == Bindings::NavigationType::Traverse) { + // 1. Set navigation's current entry index to the result of getting the navigation API entry index of destinationSHE within navigation. + m_current_entry_index = get_the_navigation_api_entry_index(destination_she); + + // 2. Assert: navigation's current entry index is not −1. + // NOTE: This algorithm is only called for same-document traversals. + // Cross-document traversals will instead call either initialize the navigation API entries for a new document + // or update the navigation API entries for reactivation + VERIFY(m_current_entry_index != -1); + } + + // 5. Otherwise, if navigationType is "push", then: + else if (navigation_type == Bindings::NavigationType::Push) { + // 1. Set navigation's current entry index to navigation's current entry index + 1. + m_current_entry_index++; + + // 2. Let i be navigation's current entry index. + auto i = m_current_entry_index; + + // 3. While i < navigation's entry list's size: + while (i < static_cast(m_entry_list.size())) { + // 1. Append navigation's entry list[i] to disposedNHEs. + disposed_nhes.append(m_entry_list[i]); + + // 2. Set i to i + 1. + ++i; + } + + // 4. Remove all items in disposedNHEs from navigation's entry list. + m_entry_list.remove(m_current_entry_index, m_entry_list.size() - m_current_entry_index); + } + + // 6. Otherwise, if navigationType is "replace", then: + else if (navigation_type == Bindings::NavigationType::Replace) { + VERIFY(old_current_nhe != nullptr); + + // 1. Append oldCurrentNHE to disposedNHEs. + disposed_nhes.append(*old_current_nhe); + } + + // 7. If navigationType is "push" or "replace", then: + if (navigation_type == Bindings::NavigationType::Push || navigation_type == Bindings::NavigationType::Replace) { + // 1. Let newNHE be a new NavigationHistoryEntry created in the relevant realm of navigation. + // 2. Set newNHE's session history entry to destinationSHE. + auto new_nhe = NavigationHistoryEntry::create(realm, destination_she); + + VERIFY(m_current_entry_index != -1); + + // 3. Set navigation's entry list[navigation's current entry index] to newNHE. + if (m_current_entry_index < static_cast(m_entry_list.size())) + m_entry_list[m_current_entry_index] = new_nhe; + else { + VERIFY(m_current_entry_index == static_cast(m_entry_list.size())); + m_entry_list.append(new_nhe); + } + } + + // 8. If navigation's ongoing API method tracker is non-null, then notify about the committed-to entry + // given navigation's ongoing API method tracker and the current entry of navigation. + // NOTE: It is important to do this before firing the dispose or currententrychange events, + // since event handlers could start another navigation, or otherwise change the value of + // navigation's ongoing API method tracker. + if (m_ongoing_api_method_tracker != nullptr) + notify_about_the_committed_to_entry(*m_ongoing_api_method_tracker, *current_entry()); + + // 9. Prepare to run script given navigation's relevant settings object. + relevant_settings_object(*this).prepare_to_run_script(); + + // 10. Fire an event named currententrychange at navigation using NavigationCurrentEntryChangeEvent, + // with its navigationType attribute initialized to navigationType and its from initialized to oldCurrentNHE. + NavigationCurrentEntryChangeEventInit event_init = {}; + event_init.navigation_type = navigation_type; + event_init.from = old_current_nhe; + dispatch_event(NavigationCurrentEntryChangeEvent::construct_impl(realm, EventNames::currententrychange, event_init)); + + // 11. For each disposedNHE of disposedNHEs: + for (auto& disposed_nhe : disposed_nhes) { + // 1. Fire an event named dispose at disposedNHE. + disposed_nhe->dispatch_event(DOM::Event::create(realm, EventNames::dispose, {})); + } + + // 12. Clean up after running script given navigation's relevant settings object. + relevant_settings_object(*this).clean_up_after_running_script(); +} + } diff --git a/Userland/Libraries/LibWeb/HTML/Navigation.h b/Userland/Libraries/LibWeb/HTML/Navigation.h index bdcb6a6418..d9b0eb3c9b 100644 --- a/Userland/Libraries/LibWeb/HTML/Navigation.h +++ b/Userland/Libraries/LibWeb/HTML/Navigation.h @@ -121,6 +121,9 @@ public: Optional classic_history_api_state = {}); bool fire_a_download_request_navigate_event(AK::URL destination_url, UserNavigationInvolvement user_involvement, String filename); + void initialize_the_navigation_api_entries_for_a_new_document(Vector> const& new_shes, JS::NonnullGCPtr initial_she); + void update_the_navigation_api_entries_for_a_same_document_navigation(JS::NonnullGCPtr destination_she, Bindings::NavigationType); + virtual ~Navigation() override; // Internal Getters/Setters @@ -145,6 +148,7 @@ private: void resolve_the_finished_promise(JS::NonnullGCPtr); void reject_the_finished_promise(JS::NonnullGCPtr, JS::Value exception); void clean_up(JS::NonnullGCPtr); + void notify_about_the_committed_to_entry(JS::NonnullGCPtr, JS::NonnullGCPtr); bool inner_navigate_event_firing_algorithm( Bindings::NavigationType, diff --git a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp index 761af6e51f..77484af555 100644 --- a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp +++ b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp @@ -472,6 +472,77 @@ void TraversableNavigable::apply_the_history_step(int step, Optional> TraversableNavigable::get_session_history_entries_for_the_navigation_api(JS::NonnullGCPtr navigable, int target_step) +{ + // 1. Let rawEntries be the result of getting session history entries for navigable. + auto raw_entries = navigable->get_session_history_entries(); + + if (raw_entries.is_empty()) + return {}; + + // 2. Let entriesForNavigationAPI be a new empty list. + Vector> entries_for_navigation_api; + + // 3. Let startingIndex be the index of the session history entry in rawEntries who has the greatest step less than or equal to targetStep. + // FIXME: Use min/max_element algorithm or some such here + int starting_index = 0; + auto max_step = 0; + for (auto i = 0u; i < raw_entries.size(); ++i) { + auto const& entry = raw_entries[i]; + if (entry->step.has()) { + auto step = entry->step.get(); + if (step <= target_step && step > max_step) { + starting_index = static_cast(i); + } + } + } + + // 4. Append rawEntries[startingIndex] to entriesForNavigationAPI. + entries_for_navigation_api.append(raw_entries[starting_index]); + + // 5. Let startingOrigin be rawEntries[startingIndex]'s document state's origin. + auto starting_origin = raw_entries[starting_index]->document_state->origin(); + + // 6. Let i be startingIndex − 1. + auto i = starting_index - 1; + + // 7. While i > 0: + while (i > 0) { + auto& entry = raw_entries[static_cast(i)]; + // 1. If rawEntries[i]'s document state's origin is not same origin with startingOrigin, then break. + auto entry_origin = entry->document_state->origin(); + if (starting_origin.has_value() && entry_origin.has_value() && !entry_origin->is_same_origin(*starting_origin)) + break; + + // 2. Prepend rawEntries[i] to entriesForNavigationAPI. + entries_for_navigation_api.prepend(entry); + + // 3. Set i to i − 1. + --i; + } + + // 8. Set i to startingIndex + 1. + i = starting_index + 1; + + // 9. While i < rawEntries's size: + while (i < static_cast(raw_entries.size())) { + auto& entry = raw_entries[static_cast(i)]; + // 1. If rawEntries[i]'s document state's origin is not same origin with startingOrigin, then break. + auto entry_origin = entry->document_state->origin(); + if (starting_origin.has_value() && entry_origin.has_value() && !entry_origin->is_same_origin(*starting_origin)) + break; + + // 2. Append rawEntries[i] to entriesForNavigationAPI. + entries_for_navigation_api.append(entry); + + // 3. Set i to i + 1. + ++i; + } + + // 10. Return entriesForNavigationAPI. + return entries_for_navigation_api; +} + // 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 057088a492..d1c02cb2fc 100644 --- a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.h +++ b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.h @@ -73,6 +73,8 @@ private: void apply_the_history_step(int step, Optional = {}); + Vector> get_session_history_entries_for_the_navigation_api(JS::NonnullGCPtr, int); + // https://html.spec.whatwg.org/multipage/document-sequences.html#tn-current-session-history-step int m_current_session_history_step { 0 };