LibWeb: Add assorted AOs related to populating the navigation API's SHEs

This commit is contained in:
Andrew Kaster 2023-09-27 22:56:11 -06:00 committed by Alexander Kalenik
parent 49e0466a3d
commit a8091c009b
Notes: sideshowbarker 2024-07-17 11:30:05 +09:00
6 changed files with 246 additions and 1 deletions

View file

@ -3605,6 +3605,21 @@ Painting::ViewportPaintable* Document::paintable()
return static_cast<Painting::ViewportPaintable*>(Node::paintable()); return static_cast<Painting::ViewportPaintable*>(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<HTML::SessionHistoryEntry> 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 // 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<HTML::SessionHistoryEntry> entry, bool do_not_reactive, size_t script_history_length, size_t script_history_index) void Document::update_for_history_step_application(JS::NonnullGCPtr<HTML::SessionHistoryEntry> entry, bool do_not_reactive, size_t script_history_length, size_t script_history_index)
{ {

View file

@ -538,6 +538,7 @@ public:
void update_for_history_step_application(JS::NonnullGCPtr<HTML::SessionHistoryEntry>, bool do_not_reactive, size_t script_history_length, size_t script_history_index); void update_for_history_step_application(JS::NonnullGCPtr<HTML::SessionHistoryEntry>, bool do_not_reactive, size_t script_history_length, size_t script_history_index);
HashMap<AK::URL, JS::GCPtr<HTML::SharedImageRequest>>& shared_image_requests(); HashMap<AK::URL, JS::GCPtr<HTML::SharedImageRequest>>& shared_image_requests();
void restore_the_history_object_state(JS::NonnullGCPtr<HTML::SessionHistoryEntry> entry);
JS::NonnullGCPtr<Animations::DocumentTimeline> timeline(); JS::NonnullGCPtr<Animations::DocumentTimeline> timeline();

View file

@ -148,7 +148,7 @@ WebIDL::ExceptionOr<void> Navigation::update_current_entry(NavigationUpdateCurre
NavigationCurrentEntryChangeEventInit event_init = {}; NavigationCurrentEntryChangeEventInit event_init = {};
event_init.navigation_type = {}; event_init.navigation_type = {};
event_init.from = current; 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 {}; return {};
} }
@ -868,6 +868,28 @@ void Navigation::reject_the_finished_promise(JS::NonnullGCPtr<NavigationAPIMetho
clean_up(api_method_tracker); clean_up(api_method_tracker);
} }
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#notify-about-the-committed-to-entry
void Navigation::notify_about_the_committed_to_entry(JS::NonnullGCPtr<NavigationAPIMethodTracker> api_method_tracker, JS::NonnullGCPtr<NavigationHistoryEntry> 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 // https://html.spec.whatwg.org/multipage/nav-history-apis.html#inner-navigate-event-firing-algorithm
bool Navigation::inner_navigate_event_firing_algorithm( bool Navigation::inner_navigate_event_firing_algorithm(
Bindings::NavigationType navigation_type, 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), {}); 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<JS::NonnullGCPtr<SessionHistoryEntry>> const& new_shes, JS::NonnullGCPtr<SessionHistoryEntry> 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<SessionHistoryEntry> 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<JS::NonnullGCPtr<NavigationHistoryEntry>> 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<i64>(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<i64>(m_entry_list.size()))
m_entry_list[m_current_entry_index] = new_nhe;
else {
VERIFY(m_current_entry_index == static_cast<i64>(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();
}
} }

View file

@ -121,6 +121,9 @@ public:
Optional<SerializationRecord> classic_history_api_state = {}); Optional<SerializationRecord> classic_history_api_state = {});
bool fire_a_download_request_navigate_event(AK::URL destination_url, UserNavigationInvolvement user_involvement, String filename); 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<JS::NonnullGCPtr<SessionHistoryEntry>> const& new_shes, JS::NonnullGCPtr<SessionHistoryEntry> initial_she);
void update_the_navigation_api_entries_for_a_same_document_navigation(JS::NonnullGCPtr<SessionHistoryEntry> destination_she, Bindings::NavigationType);
virtual ~Navigation() override; virtual ~Navigation() override;
// Internal Getters/Setters // Internal Getters/Setters
@ -145,6 +148,7 @@ private:
void resolve_the_finished_promise(JS::NonnullGCPtr<NavigationAPIMethodTracker>); void resolve_the_finished_promise(JS::NonnullGCPtr<NavigationAPIMethodTracker>);
void reject_the_finished_promise(JS::NonnullGCPtr<NavigationAPIMethodTracker>, JS::Value exception); void reject_the_finished_promise(JS::NonnullGCPtr<NavigationAPIMethodTracker>, JS::Value exception);
void clean_up(JS::NonnullGCPtr<NavigationAPIMethodTracker>); void clean_up(JS::NonnullGCPtr<NavigationAPIMethodTracker>);
void notify_about_the_committed_to_entry(JS::NonnullGCPtr<NavigationAPIMethodTracker>, JS::NonnullGCPtr<NavigationHistoryEntry>);
bool inner_navigate_event_firing_algorithm( bool inner_navigate_event_firing_algorithm(
Bindings::NavigationType, Bindings::NavigationType,

View file

@ -472,6 +472,77 @@ void TraversableNavigable::apply_the_history_step(int step, Optional<SourceSnaps
m_current_session_history_step = target_step; m_current_session_history_step = target_step;
} }
Vector<JS::NonnullGCPtr<SessionHistoryEntry>> TraversableNavigable::get_session_history_entries_for_the_navigation_api(JS::NonnullGCPtr<Navigable> 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<JS::NonnullGCPtr<SessionHistoryEntry>> 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<int>()) {
auto step = entry->step.get<int>();
if (step <= target_step && step > max_step) {
starting_index = static_cast<int>(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<unsigned>(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<int>(raw_entries.size())) {
auto& entry = raw_entries[static_cast<unsigned>(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 // https://html.spec.whatwg.org/multipage/browsing-the-web.html#clear-the-forward-session-history
void TraversableNavigable::clear_the_forward_session_history() void TraversableNavigable::clear_the_forward_session_history()
{ {

View file

@ -73,6 +73,8 @@ private:
void apply_the_history_step(int step, Optional<SourceSnapshotParams> = {}); void apply_the_history_step(int step, Optional<SourceSnapshotParams> = {});
Vector<JS::NonnullGCPtr<SessionHistoryEntry>> get_session_history_entries_for_the_navigation_api(JS::NonnullGCPtr<Navigable>, int);
// https://html.spec.whatwg.org/multipage/document-sequences.html#tn-current-session-history-step // https://html.spec.whatwg.org/multipage/document-sequences.html#tn-current-session-history-step
int m_current_session_history_step { 0 }; int m_current_session_history_step { 0 };