mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 16:17:45 +00:00
LibWeb: Implement synchronous session history steps
This commit is contained in:
parent
d6d1485720
commit
4f088aff3d
4 changed files with 117 additions and 26 deletions
|
@ -1492,12 +1492,13 @@ WebIDL::ExceptionOr<void> Navigable::navigate_to_a_fragment(AK::URL const& url,
|
||||||
auto traversable = traversable_navigable();
|
auto traversable = traversable_navigable();
|
||||||
|
|
||||||
// 17. Append the following session history synchronous navigation steps involving navigable to traversable:
|
// 17. Append the following session history synchronous navigation steps involving navigable to traversable:
|
||||||
traversable->append_session_history_traversal_steps([this, traversable, history_entry, entry_to_replace] {
|
traversable->append_session_history_synchronous_navigation_steps(*this, [this, traversable, history_entry, entry_to_replace, navigation_id] {
|
||||||
// 1. Finalize a same-document navigation given traversable, navigable, historyEntry, and entryToReplace.
|
// 1. Finalize a same-document navigation given traversable, navigable, historyEntry, and entryToReplace.
|
||||||
finalize_a_same_document_navigation(*traversable, *this, history_entry, entry_to_replace);
|
finalize_a_same_document_navigation(*traversable, *this, history_entry, entry_to_replace);
|
||||||
|
|
||||||
// FIXME: 2. Invoke WebDriver BiDi fragment navigated with navigable's active browsing context and a new WebDriver BiDi
|
// FIXME: 2. Invoke WebDriver BiDi fragment navigated with navigable's active browsing context and a new WebDriver BiDi
|
||||||
// navigation status whose id is navigationId, url is url, and status is "complete".
|
// navigation status whose id is navigationId, url is url, and status is "complete".
|
||||||
|
(void)navigation_id;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
@ -1903,7 +1904,7 @@ void perform_url_and_history_update_steps(DOM::Document& document, AK::URL new_u
|
||||||
auto traversable = navigable->traversable_navigable();
|
auto traversable = navigable->traversable_navigable();
|
||||||
|
|
||||||
// 13. Append the following session history synchronous navigation steps involving navigable to traversable:
|
// 13. Append the following session history synchronous navigation steps involving navigable to traversable:
|
||||||
traversable->append_session_history_traversal_steps([traversable, navigable, new_entry, entry_to_replace] {
|
traversable->append_session_history_synchronous_navigation_steps(*navigable, [traversable, navigable, new_entry, entry_to_replace] {
|
||||||
// 1. Finalize a same-document navigation given traversable, navigable, newEntry, and entryToReplace.
|
// 1. Finalize a same-document navigation given traversable, navigable, newEntry, and entryToReplace.
|
||||||
finalize_a_same_document_navigation(*traversable, *navigable, new_entry, entry_to_replace);
|
finalize_a_same_document_navigation(*traversable, *navigable, new_entry, entry_to_replace);
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,10 +6,19 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Vector.h>
|
||||||
#include <LibCore/Timer.h>
|
#include <LibCore/Timer.h>
|
||||||
|
#include <LibJS/Heap/GCPtr.h>
|
||||||
|
#include <LibJS/SafeFunction.h>
|
||||||
|
#include <LibWeb/Forward.h>
|
||||||
|
|
||||||
namespace Web::HTML {
|
namespace Web::HTML {
|
||||||
|
|
||||||
|
struct SessionHistoryTraversalQueueEntry {
|
||||||
|
JS::SafeFunction<void()> steps;
|
||||||
|
JS::GCPtr<HTML::Navigable> target_navigable;
|
||||||
|
};
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/document-sequences.html#tn-session-history-traversal-queue
|
// https://html.spec.whatwg.org/multipage/document-sequences.html#tn-session-history-traversal-queue
|
||||||
class SessionHistoryTraversalQueue {
|
class SessionHistoryTraversalQueue {
|
||||||
public:
|
public:
|
||||||
|
@ -17,30 +26,47 @@ public:
|
||||||
{
|
{
|
||||||
m_timer = Core::Timer::create_single_shot(0, [this] {
|
m_timer = Core::Timer::create_single_shot(0, [this] {
|
||||||
while (m_queue.size() > 0) {
|
while (m_queue.size() > 0) {
|
||||||
auto steps = m_queue.take_first();
|
auto entry = m_queue.take_first();
|
||||||
steps();
|
entry.steps();
|
||||||
}
|
}
|
||||||
}).release_value_but_fixme_should_propagate_errors();
|
}).release_value_but_fixme_should_propagate_errors();
|
||||||
}
|
}
|
||||||
|
|
||||||
void append(JS::SafeFunction<void()> steps)
|
void append(JS::SafeFunction<void()> steps)
|
||||||
{
|
{
|
||||||
m_queue.append(move(steps));
|
m_queue.append({ move(steps), nullptr });
|
||||||
if (!m_timer->is_active()) {
|
if (!m_timer->is_active()) {
|
||||||
m_timer->start();
|
m_timer->start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void append_sync(JS::SafeFunction<void()> steps, JS::GCPtr<Navigable> target_navigable)
|
||||||
|
{
|
||||||
|
m_queue.append({ move(steps), target_navigable });
|
||||||
|
if (!m_timer->is_active()) {
|
||||||
|
m_timer->start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#sync-navigations-jump-queue
|
||||||
|
SessionHistoryTraversalQueueEntry first_synchronous_navigation_steps_with_target_navigable_not_contained_in(Vector<JS::GCPtr<Navigable>> const& list)
|
||||||
|
{
|
||||||
|
auto index = m_queue.find_first_index_if([&list](auto const& entry) -> bool {
|
||||||
|
return (entry.target_navigable != nullptr) && !list.contains_slow(entry.target_navigable);
|
||||||
|
});
|
||||||
|
return index.has_value() ? m_queue.take(*index) : SessionHistoryTraversalQueueEntry {};
|
||||||
|
}
|
||||||
|
|
||||||
void process()
|
void process()
|
||||||
{
|
{
|
||||||
while (m_queue.size() > 0) {
|
while (m_queue.size() > 0) {
|
||||||
auto steps = m_queue.take_first();
|
auto entry = m_queue.take_first();
|
||||||
steps();
|
entry.steps();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Vector<JS::SafeFunction<void()>> m_queue;
|
Vector<SessionHistoryTraversalQueueEntry> m_queue;
|
||||||
RefPtr<Core::Timer> m_timer;
|
RefPtr<Core::Timer> m_timer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -352,7 +352,6 @@ TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_history_
|
||||||
// 5. FIXME: If checkForCancelation is true, and the result of checking if unloading is canceled given navigablesCrossingDocuments, traversable, targetStep,
|
// 5. FIXME: If checkForCancelation is true, and the result of checking if unloading is canceled given navigablesCrossingDocuments, traversable, targetStep,
|
||||||
// and userInvolvementForNavigateEvents is not "continue", then return that result.
|
// and userInvolvementForNavigateEvents is not "continue", then return that result.
|
||||||
(void)check_for_cancelation;
|
(void)check_for_cancelation;
|
||||||
(void)user_involvement_for_navigate_events;
|
|
||||||
|
|
||||||
// 6. Let changingNavigables be the result of get all navigables whose current session history entry will change or reload given traversable and targetStep.
|
// 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 = move(change_or_reload_navigables);
|
auto changing_navigables = move(change_or_reload_navigables);
|
||||||
|
@ -477,12 +476,14 @@ TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_history_
|
||||||
// 7. In parallel, attempt to populate the history entry's document for targetEntry, given navigable, potentiallyTargetSpecificSourceSnapshotParams,
|
// 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
|
// 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's active window to run afterDocumentPopulated.
|
||||||
navigable->populate_session_history_entry_document(target_entry, *potentially_target_specific_source_snapshot_params, target_snapshot_params, {}, Empty {}, CSPNavigationType::Other, allow_POST, [this, after_document_populated]() mutable {
|
Platform::EventLoopPlugin::the().deferred_invoke([target_entry, potentially_target_specific_source_snapshot_params, target_snapshot_params, this, allow_POST, navigable, after_document_populated] {
|
||||||
queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), [after_document_populated]() mutable {
|
navigable->populate_session_history_entry_document(target_entry, *potentially_target_specific_source_snapshot_params, target_snapshot_params, {}, Empty {}, CSPNavigationType::Other, allow_POST, [this, after_document_populated]() mutable {
|
||||||
after_document_populated();
|
queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), [after_document_populated]() mutable {
|
||||||
});
|
after_document_populated();
|
||||||
})
|
});
|
||||||
.release_value_but_fixme_should_propagate_errors();
|
})
|
||||||
|
.release_value_but_fixme_should_propagate_errors();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// Otherwise, run afterDocumentPopulated immediately.
|
// Otherwise, run afterDocumentPopulated immediately.
|
||||||
else {
|
else {
|
||||||
|
@ -491,11 +492,35 @@ TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_history_
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: 13. Let navigablesThatMustWaitBeforeHandlingSyncNavigation be an empty set.
|
// 13. Let navigablesThatMustWaitBeforeHandlingSyncNavigation be an empty set.
|
||||||
|
Vector<JS::GCPtr<Navigable>> navigables_that_must_wait_before_handling_sync_navigation;
|
||||||
|
|
||||||
// FIXME: 14. While completedChangeJobs does not equal totalChangeJobs:
|
// 14. While completedChangeJobs does not equal totalChangeJobs:
|
||||||
while (completed_change_jobs != total_change_jobs) {
|
while (completed_change_jobs != total_change_jobs) {
|
||||||
// FIXME: 1. If traversable's running nested apply history step is false, then:
|
// NOTE: Synchronous navigations that are intended to take place before this traversal jump the queue at this point,
|
||||||
|
// so they can be added to the correct place in traversable's session history entries before this traversal
|
||||||
|
// potentially unloads their document. More details can be found here (https://html.spec.whatwg.org/multipage/browsing-the-web.html#sync-navigation-steps-queue-jumping-examples)
|
||||||
|
// 1. If traversable's running nested apply history step is false, then:
|
||||||
|
if (!m_running_nested_apply_history_step) {
|
||||||
|
// 1. While traversable's session history traversal queue's algorithm set contains one or more synchronous
|
||||||
|
// navigation steps with a target navigable not contained in navigablesThatMustWaitBeforeHandlingSyncNavigation:
|
||||||
|
// 1. Let steps be the first item in traversable's session history traversal queue's algorithm set
|
||||||
|
// that is synchronous navigation steps with a target navigable not contained in navigablesThatMustWaitBeforeHandlingSyncNavigation.
|
||||||
|
// 2. Remove steps from traversable's session history traversal queue's algorithm set.
|
||||||
|
for (auto steps = m_session_history_traversal_queue.first_synchronous_navigation_steps_with_target_navigable_not_contained_in(navigables_that_must_wait_before_handling_sync_navigation);
|
||||||
|
steps.target_navigable != nullptr;
|
||||||
|
steps = m_session_history_traversal_queue.first_synchronous_navigation_steps_with_target_navigable_not_contained_in(navigables_that_must_wait_before_handling_sync_navigation)) {
|
||||||
|
|
||||||
|
// 3. Set traversable's running nested apply history step to true.
|
||||||
|
m_running_nested_apply_history_step = true;
|
||||||
|
|
||||||
|
// 4. Run steps.
|
||||||
|
steps.steps();
|
||||||
|
|
||||||
|
// 5. Set traversable's running nested apply history step to false.
|
||||||
|
m_running_nested_apply_history_step = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AD-HOC: Since currently populate_session_history_entry_document does not run in parallel
|
// 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
|
// we call spin_until to interrupt execution of this function and let document population
|
||||||
|
@ -534,16 +559,18 @@ TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_history_
|
||||||
auto script_history_length = history_object_length_and_index.script_history_length;
|
auto script_history_length = history_object_length_and_index.script_history_length;
|
||||||
auto script_history_index = history_object_length_and_index.script_history_index;
|
auto script_history_index = history_object_length_and_index.script_history_index;
|
||||||
|
|
||||||
// FIXME: 9. Append navigable to navigablesThatMustWaitBeforeHandlingSyncNavigation.
|
// 9. Append navigable to navigablesThatMustWaitBeforeHandlingSyncNavigation.
|
||||||
|
navigables_that_must_wait_before_handling_sync_navigation.append(*navigable);
|
||||||
|
|
||||||
// 10. Let entriesForNavigationAPI be the result of getting session history entries for the navigation API given navigable and targetStep.
|
// 10. Let entriesForNavigationAPI be the result of getting session history entries for the navigation API given navigable and targetStep.
|
||||||
auto entries_for_navigation_api = get_session_history_entries_for_the_navigation_api(*navigable, target_step);
|
auto entries_for_navigation_api = get_session_history_entries_for_the_navigation_api(*navigable, target_step);
|
||||||
|
|
||||||
// 11. Queue a global task on the navigation and traversal task source given navigable's active window to run the steps:
|
// 11. 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, script_history_length, script_history_index, entries_for_navigation_api = move(entries_for_navigation_api)]() mutable {
|
queue_global_task(Task::Source::NavigationAndTraversal, *navigable->active_window(), [&completed_change_jobs, target_entry, navigable, displayed_document, update_only = changing_navigable_continuation.update_only, script_history_length, script_history_index, entries_for_navigation_api = move(entries_for_navigation_api), user_involvement_for_navigate_events]() mutable {
|
||||||
// NOTE: This check is not in the spec but we should not continue navigation if navigable has been destroyed.
|
// NOTE: This check is not in the spec but we should not continue navigation if navigable has been destroyed.
|
||||||
if (navigable->has_been_destroyed())
|
if (navigable->has_been_destroyed()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 1. If changingNavigableContinuation's update-only is false, then:
|
// 1. If changingNavigableContinuation's update-only is false, then:
|
||||||
if (!update_only) {
|
if (!update_only) {
|
||||||
|
@ -596,15 +623,47 @@ TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_history_
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: 15. Let totalNonchangingJobs be the size of nonchangingNavigablesThatStillNeedUpdates.
|
// 15. Let totalNonchangingJobs be the size of nonchangingNavigablesThatStillNeedUpdates.
|
||||||
|
auto total_non_changing_jobs = non_changing_navigables_that_still_need_updates.size();
|
||||||
|
|
||||||
// FIXME: 16. Let completedNonchangingJobs be 0.
|
// 16. Let completedNonchangingJobs be 0.
|
||||||
|
auto completed_non_changing_jobs = 0u;
|
||||||
|
|
||||||
// FIXME: 17. Let (scriptHistoryLength, scriptHistoryIndex) be the result of getting the history object length and index given traversable and targetStep.
|
// 17. Let (scriptHistoryLength, scriptHistoryIndex) be the result of getting the history object length and index given traversable and targetStep.
|
||||||
|
auto length_and_index = get_the_history_object_length_and_index(target_step);
|
||||||
|
auto script_history_length = length_and_index.script_history_length;
|
||||||
|
auto script_history_index = length_and_index.script_history_index;
|
||||||
|
|
||||||
// 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:
|
// 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:
|
||||||
|
for (auto& navigable : non_changing_navigables_that_still_need_updates) {
|
||||||
|
queue_global_task(Task::Source::NavigationAndTraversal, *navigable->active_window(), [&] {
|
||||||
|
// NOTE: This check is not in the spec but we should not continue navigation if navigable has been destroyed.
|
||||||
|
if (navigable->has_been_destroyed()) {
|
||||||
|
++completed_non_changing_jobs;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: 19. Wait for completedNonchangingJobs to equal totalNonchangingJobs.
|
// 1. Let document be navigable's active document.
|
||||||
|
auto document = navigable->active_document();
|
||||||
|
|
||||||
|
// 2. Set document's history object's index to scriptHistoryIndex.
|
||||||
|
document->history()->m_index = script_history_index;
|
||||||
|
|
||||||
|
// 3. Set document's history object's length to scriptHistoryLength.
|
||||||
|
document->history()->m_length = script_history_length;
|
||||||
|
|
||||||
|
// 4. Increment completedNonchangingJobs.
|
||||||
|
++completed_non_changing_jobs;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 19. Wait for completedNonchangingJobs to equal totalNonchangingJobs.
|
||||||
|
// 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 completed_non_changing_jobs == total_non_changing_jobs;
|
||||||
|
});
|
||||||
|
|
||||||
// 20. Set traversable's current session history step to targetStep.
|
// 20. Set traversable's current session history step to targetStep.
|
||||||
m_current_session_history_step = target_step;
|
m_current_session_history_step = target_step;
|
||||||
|
|
|
@ -69,6 +69,11 @@ public:
|
||||||
m_session_history_traversal_queue.append(move(steps));
|
m_session_history_traversal_queue.append(move(steps));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void append_session_history_synchronous_navigation_steps(JS::NonnullGCPtr<Navigable> target_navigable, JS::SafeFunction<void()> steps)
|
||||||
|
{
|
||||||
|
m_session_history_traversal_queue.append_sync(move(steps), target_navigable);
|
||||||
|
}
|
||||||
|
|
||||||
void process_session_history_traversal_queue()
|
void process_session_history_traversal_queue()
|
||||||
{
|
{
|
||||||
m_session_history_traversal_queue.process();
|
m_session_history_traversal_queue.process();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue