mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-26 01:35:08 +02:00
LibWeb: Replace spin_until in apply_the_history_step with state machine
Replace the blocking spin_processing_tasks_with_source_until calls in apply_the_history_step_after_unload_check() with an event-driven ApplyHistoryStepState GC cell that tracks 5 phases, following the same pattern used by CheckUnloadingCanceledState. Key changes: - Introduce ApplyHistoryStepState with phases: WaitingForDocumentPopulation, ProcessingContinuations, WaitingForChangeJobCompletion, WaitingForNonChangingJobs and Completed - Add on_complete callbacks to apply_the_push_or_replace_history_step, finalize_a_same_document_navigation, finalize_a_cross_document_navigation, and update_for_navigable_creation_or_destruction - Remove spin_until from Document::open() - Use null-document tasks for non-changing navigable updates and document unload/destroy to avoid stuck tasks when documents become non-fully-active - Defer completely_finish_loading when document has no navigable yet, and re-trigger post-load steps in activate_history_entry for documents that completed loading before activation Co-Authored-By: Shannon Booth <shannon@serenityos.org>
This commit is contained in:
committed by
Alexander Kalenik
parent
b36f2361f1
commit
2a69fd4c52
Notes:
github-actions[bot]
2026-03-31 07:49:17 +00:00
Author: https://github.com/kalenikaliaksandr Commit: https://github.com/LadybirdBrowser/ladybird/commit/2a69fd4c528 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/8687 Reviewed-by: https://github.com/shannonbooth
@@ -51,6 +51,8 @@ void TraversableNavigable::visit_edges(Cell::Visitor& visitor)
|
||||
visitor.visit(m_session_history_entries);
|
||||
visitor.visit(m_session_history_traversal_queue);
|
||||
visitor.visit(m_storage_shed);
|
||||
visitor.visit(m_apply_history_step_state);
|
||||
visitor.visit(m_paused_apply_history_step_state);
|
||||
}
|
||||
|
||||
static OrderedHashTable<TraversableNavigable*>& user_agent_top_level_traversable_set()
|
||||
@@ -440,119 +442,187 @@ struct ChangingNavigableContinuationState : public JS::Cell {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(ChangingNavigableContinuationState);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#apply-the-history-step
|
||||
void TraversableNavigable::apply_the_history_step(
|
||||
int step,
|
||||
bool check_for_cancelation,
|
||||
GC::Ptr<SourceSnapshotParams> source_snapshot_params,
|
||||
GC::Ptr<Navigable> initiator_to_check,
|
||||
IGNORE_USE_IN_ESCAPING_LAMBDA UserNavigationInvolvement user_involvement,
|
||||
IGNORE_USE_IN_ESCAPING_LAMBDA Optional<Bindings::NavigationType> navigation_type,
|
||||
IGNORE_USE_IN_ESCAPING_LAMBDA SynchronousNavigation synchronous_navigation,
|
||||
GC::Ptr<GC::Function<void(HistoryStepResult)>> on_complete)
|
||||
{
|
||||
// FIXME: 1. Assert: This is running within traversable's session history traversal queue.
|
||||
class ApplyHistoryStepState : public GC::Cell {
|
||||
GC_CELL(ApplyHistoryStepState, GC::Cell);
|
||||
GC_DECLARE_ALLOCATOR(ApplyHistoryStepState);
|
||||
|
||||
// 2. Let targetStep be the result of getting the used step given traversable and step.
|
||||
auto target_step = get_the_used_step(step);
|
||||
public:
|
||||
static constexpr int TIMEOUT_MS = 15000;
|
||||
|
||||
// Note: Calling this early so we can re-use the same list in 3.2 and 6.
|
||||
auto change_or_reload_navigables = get_all_navigables_whose_current_session_history_entry_will_change_or_reload(target_step);
|
||||
|
||||
// 3. If initiatorToCheck is not null, then:
|
||||
if (initiator_to_check != nullptr) {
|
||||
// 1. Assert: sourceSnapshotParams is not null.
|
||||
VERIFY(source_snapshot_params);
|
||||
|
||||
// 2. For each navigable of get all navigables whose current session history entry will change or reload:
|
||||
// if initiatorToCheck is not allowed by sandboxing to navigate navigable given sourceSnapshotParams, then return "initiator-disallowed".
|
||||
for (auto const& navigable : change_or_reload_navigables) {
|
||||
if (!initiator_to_check->allowed_by_sandboxing_to_navigate(*navigable, *source_snapshot_params)) {
|
||||
if (on_complete)
|
||||
on_complete->function()(HistoryStepResult::InitiatorDisallowed);
|
||||
return;
|
||||
ApplyHistoryStepState(
|
||||
GC::Ref<TraversableNavigable> traversable, int step, int target_step,
|
||||
GC::Ptr<SourceSnapshotParams> source_snapshot_params,
|
||||
UserNavigationInvolvement user_involvement,
|
||||
Optional<Bindings::NavigationType> navigation_type,
|
||||
TraversableNavigable::SynchronousNavigation synchronous_navigation,
|
||||
GC::Ref<OnApplyHistoryStepComplete> on_complete)
|
||||
: m_traversable(traversable)
|
||||
, m_step(step)
|
||||
, m_target_step(target_step)
|
||||
, m_source_snapshot_params(source_snapshot_params)
|
||||
, m_user_involvement(user_involvement)
|
||||
, m_navigation_type(navigation_type)
|
||||
, m_synchronous_navigation(synchronous_navigation)
|
||||
, m_on_complete(on_complete)
|
||||
, m_timeout(Platform::Timer::create_single_shot(heap(), TIMEOUT_MS, GC::create_function(heap(), [this] {
|
||||
if (m_phase != Phase::Completed) {
|
||||
dbgln("FIXME: ApplyHistoryStepState timed out in phase {} step={} changing={}/{} completed={}/{} cont={}/{} non_changing={}/{} url={}",
|
||||
to_underlying(m_phase), m_step,
|
||||
m_changing_navigables.size(), m_changing_navigables.size(),
|
||||
m_completed_change_jobs, m_changing_navigables.size(),
|
||||
m_continuation_index, m_continuations.size(),
|
||||
m_completed_non_changing_jobs, m_non_changing_navigables.size(),
|
||||
m_traversable->active_document() ? m_traversable->active_document()->url() : URL::URL {});
|
||||
}
|
||||
})))
|
||||
{
|
||||
m_timeout->start();
|
||||
}
|
||||
|
||||
void start();
|
||||
|
||||
void did_receive_continuation(GC::Ref<ChangingNavigableContinuationState> continuation)
|
||||
{
|
||||
m_continuations.append(continuation);
|
||||
signal_progress();
|
||||
}
|
||||
|
||||
void signal_progress()
|
||||
{
|
||||
switch (m_phase) {
|
||||
case Phase::WaitingForDocumentPopulation:
|
||||
// Population progress is tracked by m_continuations.size() + m_completed_change_jobs.
|
||||
// The caller either appended a continuation or incremented m_completed_change_jobs before calling.
|
||||
break;
|
||||
case Phase::ProcessingContinuations:
|
||||
case Phase::WaitingForChangeJobCompletion:
|
||||
++m_completed_change_jobs;
|
||||
break;
|
||||
case Phase::WaitingForNonChangingJobs:
|
||||
++m_completed_non_changing_jobs;
|
||||
break;
|
||||
case Phase::Completed:
|
||||
return;
|
||||
}
|
||||
try_advance();
|
||||
}
|
||||
|
||||
enum class Phase {
|
||||
WaitingForDocumentPopulation,
|
||||
ProcessingContinuations,
|
||||
WaitingForChangeJobCompletion,
|
||||
WaitingForNonChangingJobs,
|
||||
Completed,
|
||||
};
|
||||
|
||||
private:
|
||||
void visit_edges(Cell::Visitor& visitor) override
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_traversable);
|
||||
visitor.visit(m_source_snapshot_params);
|
||||
visitor.visit(m_on_complete);
|
||||
visitor.visit(m_timeout);
|
||||
visitor.visit(m_changing_navigables);
|
||||
visitor.visit(m_non_changing_navigables);
|
||||
visitor.visit(m_continuations);
|
||||
for (auto& navigable : m_navigables_that_must_wait_before_handling_sync_navigation)
|
||||
visitor.visit(navigable);
|
||||
}
|
||||
|
||||
void try_advance()
|
||||
{
|
||||
switch (m_phase) {
|
||||
case Phase::WaitingForDocumentPopulation:
|
||||
if (m_continuations.size() + m_completed_change_jobs == m_changing_navigables.size()) {
|
||||
m_phase = Phase::ProcessingContinuations;
|
||||
process_continuations();
|
||||
}
|
||||
break;
|
||||
case Phase::ProcessingContinuations:
|
||||
case Phase::WaitingForChangeJobCompletion:
|
||||
if (m_completed_change_jobs == m_changing_navigables.size() && m_continuation_index >= m_continuations.size()) {
|
||||
m_phase = Phase::WaitingForNonChangingJobs;
|
||||
enter_waiting_for_non_changing_jobs();
|
||||
}
|
||||
break;
|
||||
case Phase::WaitingForNonChangingJobs:
|
||||
if (m_completed_non_changing_jobs == m_non_changing_navigables.size())
|
||||
complete();
|
||||
break;
|
||||
case Phase::Completed:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Let navigablesCrossingDocuments be the result of getting all navigables that might experience a cross-document traversal given traversable and targetStep.
|
||||
auto navigables_crossing_documents = get_all_navigables_that_might_experience_a_cross_document_traversal(target_step);
|
||||
void process_continuations();
|
||||
void enter_waiting_for_non_changing_jobs();
|
||||
void complete();
|
||||
|
||||
// 5. If checkForCancelation is true, and the result of checking if unloading is canceled given navigablesCrossingDocuments, traversable, targetStep,
|
||||
// and userInvolvement is not "continue", then return that result.
|
||||
if (check_for_cancelation) {
|
||||
check_if_unloading_is_canceled(navigables_crossing_documents, *this, target_step, user_involvement,
|
||||
GC::create_function(heap(), [this, step, target_step, source_snapshot_params, changing_navigables = move(change_or_reload_navigables), user_involvement, navigation_type, synchronous_navigation, on_complete](CheckIfUnloadingIsCanceledResult result) mutable {
|
||||
if (result == CheckIfUnloadingIsCanceledResult::CanceledByBeforeUnload) {
|
||||
if (on_complete)
|
||||
on_complete->function()(HistoryStepResult::CanceledByBeforeUnload);
|
||||
return;
|
||||
}
|
||||
if (result == CheckIfUnloadingIsCanceledResult::CanceledByNavigate) {
|
||||
if (on_complete)
|
||||
on_complete->function()(HistoryStepResult::CanceledByNavigate);
|
||||
return;
|
||||
}
|
||||
apply_the_history_step_after_unload_check(step, target_step, source_snapshot_params, move(changing_navigables), user_involvement, navigation_type, synchronous_navigation, on_complete);
|
||||
}));
|
||||
return;
|
||||
}
|
||||
Phase m_phase { Phase::WaitingForDocumentPopulation };
|
||||
GC::Ref<TraversableNavigable> m_traversable;
|
||||
int m_step;
|
||||
int m_target_step;
|
||||
GC::Ptr<SourceSnapshotParams> m_source_snapshot_params;
|
||||
UserNavigationInvolvement m_user_involvement;
|
||||
Optional<Bindings::NavigationType> m_navigation_type;
|
||||
TraversableNavigable::SynchronousNavigation m_synchronous_navigation;
|
||||
GC::Ptr<OnApplyHistoryStepComplete> m_on_complete;
|
||||
GC::Ref<Platform::Timer> m_timeout;
|
||||
|
||||
// 6. Let changingNavigables be the result of get all navigables whose current session history entry will change or reload given traversable and targetStep.
|
||||
apply_the_history_step_after_unload_check(step, target_step, source_snapshot_params, move(change_or_reload_navigables), user_involvement, navigation_type, synchronous_navigation, on_complete);
|
||||
}
|
||||
Vector<GC::Ref<Navigable>> m_changing_navigables;
|
||||
Vector<GC::Ref<Navigable>> m_non_changing_navigables;
|
||||
|
||||
void TraversableNavigable::apply_the_history_step_after_unload_check(
|
||||
int step,
|
||||
int target_step,
|
||||
GC::Ptr<SourceSnapshotParams> source_snapshot_params,
|
||||
Vector<GC::Root<Navigable>> changing_navigables,
|
||||
UserNavigationInvolvement user_involvement,
|
||||
IGNORE_USE_IN_ESCAPING_LAMBDA Optional<Bindings::NavigationType> navigation_type,
|
||||
IGNORE_USE_IN_ESCAPING_LAMBDA SynchronousNavigation synchronous_navigation,
|
||||
GC::Ptr<GC::Function<void(HistoryStepResult)>> on_complete)
|
||||
size_t m_completed_change_jobs { 0 };
|
||||
Vector<GC::Ref<ChangingNavigableContinuationState>> m_continuations;
|
||||
size_t m_continuation_index { 0 };
|
||||
|
||||
RefPtr<Core::Promise<Empty>> m_pending_sync_nav_promise;
|
||||
HashTable<GC::Ref<Navigable>> m_navigables_that_must_wait_before_handling_sync_navigation;
|
||||
|
||||
size_t m_completed_non_changing_jobs { 0 };
|
||||
};
|
||||
|
||||
GC_DEFINE_ALLOCATOR(ApplyHistoryStepState);
|
||||
|
||||
void ApplyHistoryStepState::start()
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 7. Let nonchangingNavigablesThatStillNeedUpdates be the result of getting all navigables that only need history object length/index update given traversable and targetStep.
|
||||
auto non_changing_navigables_that_still_need_updates = get_all_navigables_that_only_need_history_object_length_index_update(target_step);
|
||||
auto non_changing_navigables = m_traversable->get_all_navigables_that_only_need_history_object_length_index_update(m_target_step);
|
||||
for (auto& nav : non_changing_navigables)
|
||||
m_non_changing_navigables.append(*nav);
|
||||
|
||||
// 8. For each navigable of changingNavigables:
|
||||
auto changing_navigables = m_traversable->get_all_navigables_whose_current_session_history_entry_will_change_or_reload(m_target_step);
|
||||
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);
|
||||
auto target_entry = navigable->get_the_target_history_entry(m_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".
|
||||
navigable->set_ongoing_navigation(Traversal::Tag);
|
||||
navigable->set_ongoing_navigation(HTML::Navigable::Traversal::Tag);
|
||||
|
||||
m_changing_navigables.append(*navigable);
|
||||
}
|
||||
|
||||
// 9. Let totalChangeJobs be the size of changingNavigables.
|
||||
IGNORE_USE_IN_ESCAPING_LAMBDA auto total_change_jobs = changing_navigables.size();
|
||||
|
||||
// 10. Let completedChangeJobs be 0.
|
||||
IGNORE_USE_IN_ESCAPING_LAMBDA size_t completed_change_jobs = 0;
|
||||
|
||||
// 11. Let changingNavigableContinuations be an empty queue of changing navigable continuation states.
|
||||
// NOTE: This queue is used to split the operations on changingNavigables into two parts. Specifically, changingNavigableContinuations holds data for the second part.
|
||||
IGNORE_USE_IN_ESCAPING_LAMBDA Queue<GC::Root<ChangingNavigableContinuationState>> 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) {
|
||||
// 12. For each navigable of changingNavigables, queue a global task on the navigation and traversal task source.
|
||||
for (auto& navigable : m_changing_navigables) {
|
||||
// AD-HOC: If the navigable has been destroyed, or has no active window, skip it.
|
||||
// We must increment completed_change_jobs here rather than relying on the queued
|
||||
// task, because Document::destroy() removes tasks associated with a document from
|
||||
// the task queue, which can cause those tasks to never run.
|
||||
if (navigable->has_been_destroyed() || !navigable->active_window()) {
|
||||
completed_change_jobs++;
|
||||
++m_completed_change_jobs;
|
||||
signal_progress();
|
||||
continue;
|
||||
}
|
||||
queue_global_task(Task::Source::NavigationAndTraversal, *navigable->active_window(), GC::create_function(heap(), [&] {
|
||||
queue_global_task(Task::Source::NavigationAndTraversal, *navigable->active_window(), GC::create_function(heap(), [this, navigable] {
|
||||
// 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_change_jobs++;
|
||||
++m_completed_change_jobs;
|
||||
signal_progress();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -563,7 +633,7 @@ void TraversableNavigable::apply_the_history_step_after_unload_check(
|
||||
auto target_entry = navigable->current_session_history_entry();
|
||||
|
||||
// 3. Let changingNavigableContinuation be a changing navigable continuation state with:
|
||||
auto changing_navigable_continuation = vm.heap().allocate<ChangingNavigableContinuationState>();
|
||||
auto changing_navigable_continuation = heap().allocate<ChangingNavigableContinuationState>();
|
||||
changing_navigable_continuation->displayed_document = displayed_entry->document();
|
||||
changing_navigable_continuation->target_entry = target_entry;
|
||||
changing_navigable_continuation->navigable = navigable;
|
||||
@@ -571,20 +641,20 @@ void TraversableNavigable::apply_the_history_step_after_unload_check(
|
||||
changing_navigable_continuation->population_output = nullptr;
|
||||
|
||||
// 4. If displayedEntry is targetEntry and targetEntry's document state's reload pending is false, then:
|
||||
if (synchronous_navigation == SynchronousNavigation::Yes && !target_entry->document_state()->reload_pending()) {
|
||||
if (m_synchronous_navigation == TraversableNavigable::SynchronousNavigation::Yes && !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));
|
||||
did_receive_continuation(changing_navigable_continuation);
|
||||
|
||||
// 3. Abort these steps.
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Switch on navigationType:
|
||||
if (navigation_type.has_value()) {
|
||||
switch (navigation_type.value()) {
|
||||
if (m_navigation_type.has_value()) {
|
||||
switch (m_navigation_type.value()) {
|
||||
case Bindings::NavigationType::Reload:
|
||||
// - "reload": Assert: targetEntry's document state's reload pending is true.
|
||||
VERIFY(target_entry->document_state()->reload_pending());
|
||||
@@ -618,15 +688,14 @@ void TraversableNavigable::apply_the_history_step_after_unload_check(
|
||||
if (!navigable->is_traversable()
|
||||
&& target_entry != navigable->current_session_history_entry()
|
||||
&& old_origin == navigable->current_session_history_entry()->document_state()->origin()) {
|
||||
|
||||
// 1. Let navigation be navigable's active window's navigation API.
|
||||
auto navigation = active_window()->navigation();
|
||||
auto navigation = m_traversable->active_window()->navigation();
|
||||
|
||||
// 2. Fire a traverse navigate event at navigation given targetEntry and userInvolvement.
|
||||
navigation->fire_a_traverse_navigate_event(*target_entry, user_involvement);
|
||||
navigation->fire_a_traverse_navigate_event(*target_entry, m_user_involvement);
|
||||
}
|
||||
|
||||
auto after_document_populated = [old_origin, changing_navigable_continuation, &changing_navigable_continuations, target_entry](GC::Ptr<PopulateSessionHistoryEntryDocumentOutput> output) mutable {
|
||||
auto after_document_populated = GC::create_function(heap(), [this, old_origin, changing_navigable_continuation, target_entry](GC::Ptr<PopulateSessionHistoryEntryDocumentOutput> output) mutable {
|
||||
changing_navigable_continuation->population_output = output;
|
||||
changing_navigable_continuation->old_origin = old_origin;
|
||||
|
||||
@@ -644,8 +713,8 @@ void TraversableNavigable::apply_the_history_step_after_unload_check(
|
||||
// NOTE: Steps 2-3 are deferred to after_potential_unload to avoid exposing mutations during unload.
|
||||
|
||||
// 4. Enqueue changingNavigableContinuation on changingNavigableContinuations.
|
||||
changing_navigable_continuations.enqueue(move(changing_navigable_continuation));
|
||||
};
|
||||
did_receive_continuation(changing_navigable_continuation);
|
||||
});
|
||||
|
||||
// 8. If targetEntry's document is null, or targetEntry's document state's reload pending is true, then:
|
||||
if (!target_entry->document() || target_entry->document_state()->reload_pending()) {
|
||||
@@ -655,12 +724,11 @@ void TraversableNavigable::apply_the_history_step_after_unload_check(
|
||||
auto target_snapshot_params = navigable->snapshot_target_snapshot_params();
|
||||
|
||||
// 3. Let potentiallyTargetSpecificSourceSnapshotParams be sourceSnapshotParams.
|
||||
GC::Ptr<SourceSnapshotParams> potentially_target_specific_source_snapshot_params = source_snapshot_params;
|
||||
auto potentially_target_specific_source_snapshot_params = m_source_snapshot_params;
|
||||
|
||||
// 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) {
|
||||
if (!potentially_target_specific_source_snapshot_params)
|
||||
potentially_target_specific_source_snapshot_params = navigable->active_document()->snapshot_source_snapshot_params();
|
||||
}
|
||||
|
||||
// 5. Set targetEntry's document state's reload pending to false.
|
||||
target_entry->document_state()->set_reload_pending(false);
|
||||
@@ -686,31 +754,19 @@ void TraversableNavigable::apply_the_history_step_after_unload_check(
|
||||
// targetSnapshotParams, userInvolvement, 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.
|
||||
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(this->heap(), [input_url = move(input_url), input_document_resource = move(input_document_resource), input_request_referrer = move(input_request_referrer), input_request_referrer_policy, input_initiator_origin = move(input_initiator_origin), input_origin = move(input_origin), input_history_policy_container = move(input_history_policy_container), input_about_base_url = move(input_about_base_url), input_navigable_target_name = move(input_navigable_target_name), input_ever_populated, potentially_target_specific_source_snapshot_params, target_snapshot_params, this, allow_POST, navigable, after_document_populated = GC::create_function(this->heap(), move(after_document_populated)), user_involvement] {
|
||||
auto signal_to_continue_session_history_processing = Core::Promise<Empty>::construct();
|
||||
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(heap(), [input_url = move(input_url), input_document_resource = move(input_document_resource), input_request_referrer = move(input_request_referrer), input_request_referrer_policy, input_initiator_origin = move(input_initiator_origin), input_origin = move(input_origin), input_history_policy_container = move(input_history_policy_container), input_about_base_url = move(input_about_base_url), input_navigable_target_name = move(input_navigable_target_name), input_ever_populated, potentially_target_specific_source_snapshot_params, target_snapshot_params, this, allow_POST, navigable, after_document_populated, user_involvement = m_user_involvement] {
|
||||
auto signal = Core::Promise<Empty>::construct();
|
||||
navigable->populate_session_history_entry_document(
|
||||
move(input_url),
|
||||
move(input_document_resource),
|
||||
move(input_request_referrer),
|
||||
input_request_referrer_policy,
|
||||
move(input_initiator_origin),
|
||||
move(input_origin),
|
||||
input_history_policy_container,
|
||||
move(input_about_base_url),
|
||||
move(input_navigable_target_name),
|
||||
false, // reload_pending was set to false above
|
||||
input_ever_populated,
|
||||
*potentially_target_specific_source_snapshot_params,
|
||||
target_snapshot_params,
|
||||
user_involvement,
|
||||
signal_to_continue_session_history_processing,
|
||||
{},
|
||||
Navigable::NullOrError {},
|
||||
ContentSecurityPolicy::Directives::Directive::NavigationType::Other,
|
||||
allow_POST,
|
||||
move(input_url), move(input_document_resource), move(input_request_referrer),
|
||||
input_request_referrer_policy, move(input_initiator_origin), move(input_origin),
|
||||
input_history_policy_container, move(input_about_base_url), move(input_navigable_target_name),
|
||||
false, input_ever_populated,
|
||||
*potentially_target_specific_source_snapshot_params, target_snapshot_params,
|
||||
user_involvement, signal, {}, Navigable::NullOrError {},
|
||||
ContentSecurityPolicy::Directives::Directive::NavigationType::Other, allow_POST,
|
||||
GC::create_function(this->heap(), [this, after_document_populated](GC::Ptr<PopulateSessionHistoryEntryDocumentOutput> output) {
|
||||
VERIFY(active_window());
|
||||
queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), GC::create_function(this->heap(), [after_document_populated, output]() {
|
||||
VERIFY(m_traversable->active_window());
|
||||
queue_global_task(Task::Source::NavigationAndTraversal, *m_traversable->active_window(), GC::create_function(heap(), [after_document_populated, output]() {
|
||||
after_document_populated->function()(output);
|
||||
}));
|
||||
}));
|
||||
@@ -718,97 +774,109 @@ void TraversableNavigable::apply_the_history_step_after_unload_check(
|
||||
}
|
||||
// Otherwise, run afterDocumentPopulated immediately.
|
||||
else {
|
||||
after_document_populated(nullptr);
|
||||
after_document_populated->function()(nullptr);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
auto check_if_document_population_tasks_completed = GC::create_function(heap(), [&] {
|
||||
return changing_navigable_continuations.size() + completed_change_jobs == total_change_jobs;
|
||||
});
|
||||
try_advance();
|
||||
}
|
||||
|
||||
if (synchronous_navigation == SynchronousNavigation::Yes) {
|
||||
// NOTE: Synchronous navigation should never require document population, so it is safe to process only NavigationAndTraversal source.
|
||||
main_thread_event_loop().spin_processing_tasks_with_source_until(Task::Source::NavigationAndTraversal, check_if_document_population_tasks_completed);
|
||||
} else {
|
||||
// NOTE: Process all task sources while waiting because reloading or back/forward navigation might require fetching to populate a document.
|
||||
main_thread_event_loop().spin_until(check_if_document_population_tasks_completed);
|
||||
}
|
||||
|
||||
// 13. Let navigablesThatMustWaitBeforeHandlingSyncNavigation be an empty set.
|
||||
HashTable<GC::Ref<Navigable>> navigables_that_must_wait_before_handling_sync_navigation;
|
||||
|
||||
// 14. While completedChangeJobs does not equal totalChangeJobs:
|
||||
while (!changing_navigable_continuations.is_empty()) {
|
||||
void ApplyHistoryStepState::process_continuations()
|
||||
{
|
||||
for (;;) {
|
||||
// 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) {
|
||||
if (!m_traversable->m_paused_apply_history_step_state) {
|
||||
// 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 entry = m_session_history_traversal_queue->first_synchronous_navigation_steps_with_target_navigable_not_contained_in(navigables_that_must_wait_before_handling_sync_navigation);
|
||||
entry;
|
||||
entry = m_session_history_traversal_queue->first_synchronous_navigation_steps_with_target_navigable_not_contained_in(navigables_that_must_wait_before_handling_sync_navigation)) {
|
||||
while (true) {
|
||||
auto entry = m_traversable->m_session_history_traversal_queue->first_synchronous_navigation_steps_with_target_navigable_not_contained_in(m_navigables_that_must_wait_before_handling_sync_navigation);
|
||||
if (!entry)
|
||||
break;
|
||||
|
||||
// 3. Set traversable's running nested apply history step to true.
|
||||
m_running_nested_apply_history_step = true;
|
||||
VERIFY(!m_traversable->m_paused_apply_history_step_state);
|
||||
m_traversable->m_paused_apply_history_step_state = this;
|
||||
|
||||
// 4. Run steps.
|
||||
entry->execute_steps()->await().release_value_but_fixme_should_propagate_errors();
|
||||
auto promise = Core::Promise<Empty>::construct();
|
||||
entry->execute_steps(promise);
|
||||
|
||||
// 5. Set traversable's running nested apply history step to false.
|
||||
m_running_nested_apply_history_step = false;
|
||||
// GC safety: `this` is kept alive by m_paused_apply_history_step_state (visited).
|
||||
// The promise is kept alive by m_pending_sync_nav_promise (RefPtr).
|
||||
VERIFY(!m_pending_sync_nav_promise);
|
||||
m_pending_sync_nav_promise = promise;
|
||||
promise->when_resolved([this](Empty) {
|
||||
// 5. Set traversable's running nested apply history step to false.
|
||||
VERIFY(m_pending_sync_nav_promise);
|
||||
m_pending_sync_nav_promise = nullptr;
|
||||
m_traversable->m_apply_history_step_state = this;
|
||||
m_traversable->m_paused_apply_history_step_state = nullptr;
|
||||
process_continuations();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Let changingNavigableContinuation be the result of dequeuing from changingNavigableContinuations.
|
||||
auto changing_navigable_continuation = changing_navigable_continuations.dequeue();
|
||||
if (m_continuation_index == m_continuations.size()) {
|
||||
if (m_phase == Phase::ProcessingContinuations) {
|
||||
m_phase = Phase::WaitingForChangeJobCompletion;
|
||||
try_advance();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. If changingNavigableContinuation is nothing, then continue.
|
||||
|
||||
auto continuation = m_continuations[m_continuation_index++];
|
||||
|
||||
// 4. Let displayedDocument be changingNavigableContinuation's displayed document.
|
||||
auto displayed_document = changing_navigable_continuation->displayed_document;
|
||||
auto displayed_document = continuation->displayed_document;
|
||||
|
||||
// 5. Let targetEntry be changingNavigableContinuation's target entry.
|
||||
GC::Ptr<PopulateSessionHistoryEntryDocumentOutput> const population_output = changing_navigable_continuation->population_output;
|
||||
auto old_origin = changing_navigable_continuation->old_origin;
|
||||
auto population_output = continuation->population_output;
|
||||
auto old_origin = continuation->old_origin;
|
||||
|
||||
// 6. Let navigable be changingNavigableContinuation's navigable.
|
||||
auto navigable = changing_navigable_continuation->navigable;
|
||||
auto navigable = continuation->navigable;
|
||||
|
||||
// AD-HOC: We should not continue navigation if navigable has been destroyed.
|
||||
if (navigable->has_been_destroyed())
|
||||
if (navigable->has_been_destroyed()) {
|
||||
signal_progress();
|
||||
continue;
|
||||
|
||||
}
|
||||
// AD-HOC: The displayed document may have been destroyed during the nested step execution above.
|
||||
if (!displayed_document->navigable())
|
||||
if (!displayed_document->navigable()) {
|
||||
signal_progress();
|
||||
continue;
|
||||
}
|
||||
|
||||
// AD-HOC: We re-compute targetStep here, since it might have changed since the last time we computed it.
|
||||
// This can happen if navigables are destroyed while we wait for tasks to complete.
|
||||
target_step = get_the_used_step(step);
|
||||
m_target_step = m_traversable->get_the_used_step(m_step);
|
||||
|
||||
// 7. Let (scriptHistoryLength, scriptHistoryIndex) be the result of getting the history object length and index given traversable and targetStep.
|
||||
auto history_object_length_and_index = get_the_history_object_length_and_index(target_step);
|
||||
auto history_object_length_and_index = m_traversable->get_the_history_object_length_and_index(m_target_step);
|
||||
auto script_history_length = history_object_length_and_index.script_history_length;
|
||||
auto script_history_index = history_object_length_and_index.script_history_index;
|
||||
|
||||
// 8. Append navigable to navigablesThatMustWaitBeforeHandlingSyncNavigation.
|
||||
navigables_that_must_wait_before_handling_sync_navigation.set(*navigable);
|
||||
m_navigables_that_must_wait_before_handling_sync_navigation.set(*navigable);
|
||||
|
||||
// 9. 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 = m_traversable->get_session_history_entries_for_the_navigation_api(*navigable, m_target_step);
|
||||
|
||||
// NOTE: Steps 10 and 11 come after step 12.
|
||||
|
||||
// 12. In both cases, let afterPotentialUnloads be the following steps:
|
||||
bool const update_only = changing_navigable_continuation->update_only;
|
||||
GC::Ptr<SessionHistoryEntry> const target_entry = changing_navigable_continuation->target_entry;
|
||||
auto after_potential_unload = GC::create_function(this->heap(), [navigable, update_only, target_entry, population_output, old_origin, displayed_document, &completed_change_jobs, script_history_length, script_history_index, entries_for_navigation_api = move(entries_for_navigation_api), &heap = this->heap(), navigation_type] {
|
||||
bool const update_only = continuation->update_only;
|
||||
GC::Ptr<SessionHistoryEntry> const target_entry = continuation->target_entry;
|
||||
auto after_potential_unload = GC::create_function(heap(), [this, navigable, update_only, target_entry, population_output, old_origin, displayed_document, script_history_length, script_history_index, entries_for_navigation_api = move(entries_for_navigation_api), navigation_type = m_navigation_type] {
|
||||
if (population_output)
|
||||
population_output->apply_to(*target_entry);
|
||||
|
||||
@@ -836,7 +904,7 @@ void TraversableNavigable::apply_the_history_step_after_unload_check(
|
||||
}
|
||||
|
||||
// 1. Let previousEntry be navigable's active session history entry.
|
||||
GC::Ptr<SessionHistoryEntry> const previous_entry = navigable->active_session_history_entry();
|
||||
auto previous_entry = navigable->active_session_history_entry();
|
||||
|
||||
// 2. If changingNavigableContinuation's update-only is false, then activate history entry targetEntry for navigable.
|
||||
if (!update_only)
|
||||
@@ -846,76 +914,70 @@ void TraversableNavigable::apply_the_history_step_after_unload_check(
|
||||
// targetEntry's document, targetEntry, changingNavigableContinuation's update-only, scriptHistoryLength,
|
||||
// scriptHistoryIndex, navigationType, entriesForNavigationAPI, and previousEntry.
|
||||
auto update_document = [script_history_length, script_history_index, entries_for_navigation_api = move(entries_for_navigation_api), target_entry, update_only, navigation_type, previous_entry] {
|
||||
target_entry->document()->update_for_history_step_application(*target_entry, update_only, script_history_length, script_history_index, navigation_type, entries_for_navigation_api, previous_entry);
|
||||
target_entry->document()->update_for_history_step_application(*target_entry, update_only, script_history_length, script_history_index, navigation_type, entries_for_navigation_api, previous_entry, navigation_type.has_value());
|
||||
};
|
||||
|
||||
// 3. If targetEntry's document is equal to displayedDocument, then perform updateDocument.
|
||||
// 4. If targetEntry's document is equal to displayedDocument, then perform updateDocument.
|
||||
if (target_entry->document().ptr() == displayed_document.ptr()) {
|
||||
update_document();
|
||||
}
|
||||
// 5. Otherwise, queue a global task on the navigation and traversal task source given targetEntry's document's relevant global object to perform updateDocument
|
||||
else {
|
||||
queue_global_task(Task::Source::NavigationAndTraversal, relevant_global_object(*target_entry->document()), GC::create_function(heap, move(update_document)));
|
||||
queue_global_task(Task::Source::NavigationAndTraversal, relevant_global_object(*target_entry->document()), GC::create_function(heap(), move(update_document)));
|
||||
}
|
||||
|
||||
// 6. Increment completedChangeJobs.
|
||||
completed_change_jobs++;
|
||||
signal_progress();
|
||||
});
|
||||
|
||||
auto populated_document = population_output ? population_output->document : target_entry->document();
|
||||
|
||||
// 10. If changingNavigableContinuation's update-only is true, or targetEntry's document is displayedDocument, then:
|
||||
if (changing_navigable_continuation->update_only || populated_document.ptr() == displayed_document.ptr()) {
|
||||
if (continuation->update_only || populated_document.ptr() == displayed_document.ptr()) {
|
||||
// 1. Set the ongoing navigation for navigable to null.
|
||||
navigable->set_ongoing_navigation({});
|
||||
|
||||
// 2. Queue a global task on the navigation and traversal task source given navigable's active window to perform afterPotentialUnloads.
|
||||
VERIFY(navigable->active_window());
|
||||
queue_global_task(Task::Source::NavigationAndTraversal, *navigable->active_window(), after_potential_unload);
|
||||
}
|
||||
// 11. Otherwise:
|
||||
else {
|
||||
// 1. Assert: navigationType is not null.
|
||||
VERIFY(navigation_type.has_value());
|
||||
VERIFY(m_navigation_type.has_value());
|
||||
|
||||
// 2. Deactivate displayedDocument, given userInvolvement, targetEntry, navigationType, and afterPotentialUnloads.
|
||||
deactivate_a_document_for_cross_document_navigation(*displayed_document, user_involvement, *target_entry, populated_document, after_potential_unload);
|
||||
deactivate_a_document_for_cross_document_navigation(*displayed_document, m_user_involvement, *target_entry, populated_document, after_potential_unload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main_thread_event_loop().spin_processing_tasks_with_source_until(Task::Source::NavigationAndTraversal, GC::create_function(heap(), [&] {
|
||||
return completed_change_jobs == total_change_jobs;
|
||||
}));
|
||||
|
||||
// 15. Let totalNonchangingJobs be the size of nonchangingNavigablesThatStillNeedUpdates.
|
||||
IGNORE_USE_IN_ESCAPING_LAMBDA auto total_non_changing_jobs = non_changing_navigables_that_still_need_updates.size();
|
||||
|
||||
// 16. Let completedNonchangingJobs be 0.
|
||||
IGNORE_USE_IN_ESCAPING_LAMBDA auto completed_non_changing_jobs = 0u;
|
||||
|
||||
void ApplyHistoryStepState::enter_waiting_for_non_changing_jobs()
|
||||
{
|
||||
// AD-HOC: We re-compute targetStep here, since it might have changed since the last time we computed it.
|
||||
// This can happen if navigables are destroyed while we wait for tasks to complete.
|
||||
target_step = get_the_used_step(step);
|
||||
m_target_step = m_traversable->get_the_used_step(m_step);
|
||||
|
||||
// 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);
|
||||
IGNORE_USE_IN_ESCAPING_LAMBDA auto script_history_length = length_and_index.script_history_length;
|
||||
IGNORE_USE_IN_ESCAPING_LAMBDA auto script_history_index = length_and_index.script_history_index;
|
||||
auto length_and_index = m_traversable->get_the_history_object_length_and_index(m_target_step);
|
||||
auto script_history_length = length_and_index.script_history_length;
|
||||
auto script_history_index = length_and_index.script_history_index;
|
||||
|
||||
// 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) {
|
||||
for (auto& navigable : m_non_changing_navigables) {
|
||||
// AD-HOC: This check is not in the spec but we should not continue navigation if navigable has been destroyed,
|
||||
// or if there's no active window.
|
||||
if (navigable->has_been_destroyed() || !navigable->active_window()) {
|
||||
++completed_non_changing_jobs;
|
||||
++m_completed_non_changing_jobs;
|
||||
continue;
|
||||
}
|
||||
|
||||
queue_global_task(Task::Source::NavigationAndTraversal, *navigable->active_window(), GC::create_function(heap(), [&] {
|
||||
// AD-HOC: This check is not in the spec but we should not continue navigation if navigable has been destroyed,
|
||||
// or if there's no active window.
|
||||
if (navigable->has_been_destroyed() || !navigable->active_window()) {
|
||||
++completed_non_changing_jobs;
|
||||
// AD-HOC: Queue with null document instead of using queue_global_task.
|
||||
// Tasks associated with a document are only runnable when fully active.
|
||||
// In the async state machine, documents can become non-fully-active between
|
||||
// queue time and execution, causing the task to be permanently stuck.
|
||||
// A null-document task is always runnable; we check validity inside.
|
||||
queue_a_task(Task::Source::NavigationAndTraversal, nullptr, nullptr, GC::create_function(heap(), [this, navigable, script_history_length, script_history_index] {
|
||||
if (navigable->has_been_destroyed() || !navigable->active_window() || !navigable->active_document()->is_fully_active()) {
|
||||
signal_progress();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -929,32 +991,115 @@ void TraversableNavigable::apply_the_history_step_after_unload_check(
|
||||
document->history()->m_length = script_history_length;
|
||||
|
||||
// 4. Increment completedNonchangingJobs.
|
||||
++completed_non_changing_jobs;
|
||||
signal_progress();
|
||||
}));
|
||||
}
|
||||
|
||||
// 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.
|
||||
main_thread_event_loop().spin_processing_tasks_with_source_until(Task::Source::NavigationAndTraversal, GC::create_function(heap(), [&] {
|
||||
return completed_non_changing_jobs == total_non_changing_jobs;
|
||||
}));
|
||||
try_advance();
|
||||
}
|
||||
|
||||
void ApplyHistoryStepState::complete()
|
||||
{
|
||||
if (m_phase == Phase::Completed)
|
||||
return;
|
||||
m_phase = Phase::Completed;
|
||||
m_timeout->stop();
|
||||
|
||||
// 20. Set traversable's current session history step to targetStep.
|
||||
m_current_session_history_step = target_step;
|
||||
m_traversable->m_current_session_history_step = m_target_step;
|
||||
|
||||
// Not in the spec:
|
||||
auto back_enabled = m_current_session_history_step > 0;
|
||||
VERIFY(m_session_history_entries.size() > 0);
|
||||
auto forward_enabled = can_go_forward();
|
||||
page().client().page_did_update_navigation_buttons_state(back_enabled, forward_enabled);
|
||||
auto back_enabled = m_traversable->m_current_session_history_step > 0;
|
||||
VERIFY(m_traversable->m_session_history_entries.size() > 0);
|
||||
auto forward_enabled = m_traversable->can_go_forward();
|
||||
m_traversable->page().client().page_did_update_navigation_buttons_state(back_enabled, forward_enabled);
|
||||
m_traversable->page().client().page_did_change_url(m_traversable->current_session_history_entry()->url());
|
||||
|
||||
page().client().page_did_change_url(current_session_history_entry()->url());
|
||||
// Clear state BEFORE on_complete, because on_complete may resolve a promise
|
||||
// that triggers the next session history traversal queue entry.
|
||||
// For nested states, the outer state is restored by the when_resolved callback
|
||||
// on the sync nav step's promise in process_continuations().
|
||||
m_traversable->m_apply_history_step_state = nullptr;
|
||||
|
||||
// 21. Return "applied".
|
||||
if (on_complete)
|
||||
on_complete->function()(HistoryStepResult::Applied);
|
||||
if (m_on_complete)
|
||||
m_on_complete->function()(HistoryStepResult::Applied);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#apply-the-history-step
|
||||
void TraversableNavigable::apply_the_history_step(
|
||||
int step,
|
||||
bool check_for_cancelation,
|
||||
GC::Ptr<SourceSnapshotParams> source_snapshot_params,
|
||||
GC::Ptr<Navigable> initiator_to_check,
|
||||
UserNavigationInvolvement user_involvement,
|
||||
Optional<Bindings::NavigationType> navigation_type,
|
||||
SynchronousNavigation synchronous_navigation,
|
||||
GC::Ref<OnApplyHistoryStepComplete> on_complete)
|
||||
{
|
||||
// FIXME: 1. Assert: This is running within traversable's session history traversal queue.
|
||||
|
||||
VERIFY(!m_apply_history_step_state || m_paused_apply_history_step_state);
|
||||
|
||||
// 2. Let targetStep be the result of getting the used step given traversable and step.
|
||||
auto target_step = get_the_used_step(step);
|
||||
|
||||
// 3. If initiatorToCheck is not null, then:
|
||||
if (initiator_to_check != nullptr) {
|
||||
// 1. Assert: sourceSnapshotParams is not null.
|
||||
VERIFY(source_snapshot_params);
|
||||
|
||||
// 2. For each navigable of get all navigables whose current session history entry will change or reload:
|
||||
// if initiatorToCheck is not allowed by sandboxing to navigate navigable given sourceSnapshotParams, then return "initiator-disallowed".
|
||||
for (auto const& navigable : get_all_navigables_whose_current_session_history_entry_will_change_or_reload(target_step)) {
|
||||
if (!initiator_to_check->allowed_by_sandboxing_to_navigate(*navigable, *source_snapshot_params)) {
|
||||
on_complete->function()(HistoryStepResult::InitiatorDisallowed);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Let navigablesCrossingDocuments be the result of getting all navigables that might experience a cross-document traversal given traversable and targetStep.
|
||||
auto navigables_crossing_documents = get_all_navigables_that_might_experience_a_cross_document_traversal(target_step);
|
||||
|
||||
// 5. If checkForCancelation is true, and the result of checking if unloading is canceled given navigablesCrossingDocuments, traversable, targetStep,
|
||||
// and userInvolvement is not "continue", then return that result.
|
||||
if (check_for_cancelation) {
|
||||
check_if_unloading_is_canceled(navigables_crossing_documents, *this, target_step, user_involvement,
|
||||
GC::create_function(heap(), [this, step, target_step, source_snapshot_params, user_involvement, navigation_type, synchronous_navigation, on_complete](CheckIfUnloadingIsCanceledResult result) mutable {
|
||||
if (result == CheckIfUnloadingIsCanceledResult::CanceledByBeforeUnload) {
|
||||
on_complete->function()(HistoryStepResult::CanceledByBeforeUnload);
|
||||
return;
|
||||
}
|
||||
if (result == CheckIfUnloadingIsCanceledResult::CanceledByNavigate) {
|
||||
on_complete->function()(HistoryStepResult::CanceledByNavigate);
|
||||
return;
|
||||
}
|
||||
apply_the_history_step_after_unload_check(step, target_step, source_snapshot_params, user_involvement, navigation_type, synchronous_navigation, on_complete);
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// 6. Let changingNavigables be the result of get all navigables whose current session history entry will change or reload given traversable and targetStep.
|
||||
apply_the_history_step_after_unload_check(step, target_step, source_snapshot_params, user_involvement, navigation_type, synchronous_navigation, on_complete);
|
||||
}
|
||||
|
||||
void TraversableNavigable::apply_the_history_step_after_unload_check(
|
||||
int step,
|
||||
int target_step,
|
||||
GC::Ptr<SourceSnapshotParams> source_snapshot_params,
|
||||
UserNavigationInvolvement user_involvement,
|
||||
Optional<Bindings::NavigationType> navigation_type,
|
||||
SynchronousNavigation synchronous_navigation,
|
||||
GC::Ref<GC::Function<void(HistoryStepResult)>> on_complete)
|
||||
{
|
||||
auto state = heap().allocate<ApplyHistoryStepState>(*this, step, target_step, source_snapshot_params,
|
||||
user_involvement, navigation_type, synchronous_navigation, on_complete);
|
||||
|
||||
VERIFY(!m_apply_history_step_state || m_paused_apply_history_step_state);
|
||||
m_apply_history_step_state = state;
|
||||
|
||||
state->start();
|
||||
}
|
||||
|
||||
class CheckUnloadingCanceledState : public GC::Cell {
|
||||
@@ -1329,9 +1474,7 @@ void TraversableNavigable::traverse_the_history_by_delta(int delta, GC::Ptr<DOM:
|
||||
}
|
||||
|
||||
// 4. Append the following session history traversal steps to traversable:
|
||||
append_session_history_traversal_steps(GC::create_function(heap(), [this, delta, source_snapshot_params, initiator_to_check, user_involvement] {
|
||||
// NB: Use Core::Promise to signal SessionHistoryTraversalQueue that it can continue to execute next entry.
|
||||
auto signal_to_continue_session_history_processing = Core::Promise<Empty>::construct();
|
||||
append_session_history_traversal_steps(GC::create_function(heap(), [this, delta, source_snapshot_params, initiator_to_check, user_involvement](NonnullRefPtr<Core::Promise<Empty>> signal) {
|
||||
// 1. Let allSteps be the result of getting all used history steps for traversable.
|
||||
auto all_steps = get_all_used_history_steps();
|
||||
|
||||
@@ -1343,32 +1486,31 @@ void TraversableNavigable::traverse_the_history_by_delta(int delta, GC::Ptr<DOM:
|
||||
|
||||
// 4. If allSteps[targetStepIndex] does not exist, then abort these steps.
|
||||
if (target_step_index >= all_steps.size()) {
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return signal_to_continue_session_history_processing;
|
||||
signal->resolve({});
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Apply the traverse history step allSteps[targetStepIndex] to traversable, given sourceSnapshotParams,
|
||||
// initiatorToCheck, and userInvolvement.
|
||||
apply_the_traverse_history_step(all_steps[target_step_index], source_snapshot_params, initiator_to_check, user_involvement,
|
||||
GC::create_function(heap(), [signal_to_continue_session_history_processing](HistoryStepResult) {
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
GC::create_function(heap(), [signal](HistoryStepResult) {
|
||||
signal->resolve({});
|
||||
}));
|
||||
return signal_to_continue_session_history_processing;
|
||||
}));
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#update-for-navigable-creation/destruction
|
||||
void TraversableNavigable::update_for_navigable_creation_or_destruction()
|
||||
void TraversableNavigable::update_for_navigable_creation_or_destruction(GC::Ref<OnApplyHistoryStepComplete> on_complete)
|
||||
{
|
||||
// 1. Let step be traversable's current session history step.
|
||||
auto step = current_session_history_step();
|
||||
|
||||
// 2. Return the result of applying the history step to traversable given false, null, null, null, and null.
|
||||
apply_the_history_step(step, false, {}, {}, UserNavigationInvolvement::None, {}, SynchronousNavigation::No, {});
|
||||
apply_the_history_step(step, false, {}, {}, UserNavigationInvolvement::None, {}, SynchronousNavigation::No, on_complete);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#apply-the-reload-history-step
|
||||
void TraversableNavigable::apply_the_reload_history_step(UserNavigationInvolvement user_involvement, GC::Ptr<GC::Function<void(HistoryStepResult)>> on_complete)
|
||||
void TraversableNavigable::apply_the_reload_history_step(UserNavigationInvolvement user_involvement, GC::Ref<GC::Function<void(HistoryStepResult)>> on_complete)
|
||||
{
|
||||
// 1. Let step be traversable's current session history step.
|
||||
auto step = current_session_history_step();
|
||||
@@ -1378,14 +1520,14 @@ void TraversableNavigable::apply_the_reload_history_step(UserNavigationInvolveme
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#apply-the-push/replace-history-step
|
||||
void TraversableNavigable::apply_the_push_or_replace_history_step(int step, HistoryHandlingBehavior history_handling, UserNavigationInvolvement user_involvement, SynchronousNavigation synchronous_navigation)
|
||||
void TraversableNavigable::apply_the_push_or_replace_history_step(int step, HistoryHandlingBehavior history_handling, UserNavigationInvolvement user_involvement, SynchronousNavigation synchronous_navigation, GC::Ref<OnApplyHistoryStepComplete> on_complete)
|
||||
{
|
||||
// 1. Return the result of applying the history step step to traversable given false, null, null, userInvolvement, and historyHandling.
|
||||
auto navigation_type = history_handling == HistoryHandlingBehavior::Replace ? Bindings::NavigationType::Replace : Bindings::NavigationType::Push;
|
||||
apply_the_history_step(step, false, {}, {}, user_involvement, navigation_type, synchronous_navigation, {});
|
||||
apply_the_history_step(step, false, {}, {}, user_involvement, navigation_type, synchronous_navigation, on_complete);
|
||||
}
|
||||
|
||||
void TraversableNavigable::apply_the_traverse_history_step(int step, GC::Ptr<SourceSnapshotParams> source_snapshot_params, GC::Ptr<Navigable> initiator_to_check, UserNavigationInvolvement user_involvement, GC::Ptr<GC::Function<void(HistoryStepResult)>> on_complete)
|
||||
void TraversableNavigable::apply_the_traverse_history_step(int step, GC::Ptr<SourceSnapshotParams> source_snapshot_params, GC::Ptr<Navigable> initiator_to_check, UserNavigationInvolvement user_involvement, GC::Ref<GC::Function<void(HistoryStepResult)>> on_complete)
|
||||
{
|
||||
// 1. Return the result of applying the history step step to traversable given true, sourceSnapshotParams, initiatorToCheck, userInvolvement, and "traverse".
|
||||
apply_the_history_step(step, true, source_snapshot_params, initiator_to_check, user_involvement, Bindings::NavigationType::Traverse, SynchronousNavigation::No, on_complete);
|
||||
@@ -1419,9 +1561,7 @@ void TraversableNavigable::definitely_close_top_level_traversable()
|
||||
return;
|
||||
|
||||
// 3. Append the following session history traversal steps to traversable:
|
||||
append_session_history_traversal_steps(GC::create_function(heap(), [this] {
|
||||
// NB: Use Core::Promise to signal SessionHistoryTraversalQueue that it can continue to execute next entry.
|
||||
auto signal_to_continue_session_history_processing = Core::Promise<Empty>::construct();
|
||||
append_session_history_traversal_steps(GC::create_function(heap(), [this](NonnullRefPtr<Core::Promise<Empty>> signal) {
|
||||
// 1. Let afterAllUnloads be an algorithm step which destroys traversable.
|
||||
auto after_all_unloads = GC::create_function(heap(), [this] {
|
||||
destroy_top_level_traversable();
|
||||
@@ -1429,8 +1569,7 @@ void TraversableNavigable::definitely_close_top_level_traversable()
|
||||
|
||||
// 2. Unload a document and its descendants given traversable's active document, null, and afterAllUnloads.
|
||||
active_document()->unload_a_document_and_its_descendants({}, after_all_unloads);
|
||||
signal_to_continue_session_history_processing->resolve({});
|
||||
return signal_to_continue_session_history_processing;
|
||||
signal->resolve({});
|
||||
}));
|
||||
}));
|
||||
}
|
||||
@@ -1475,11 +1614,13 @@ void TraversableNavigable::destroy_top_level_traversable()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#finalize-a-same-document-navigation
|
||||
void finalize_a_same_document_navigation(GC::Ref<TraversableNavigable> traversable, GC::Ref<Navigable> target_navigable, GC::Ref<SessionHistoryEntry> target_entry, GC::Ptr<SessionHistoryEntry> entry_to_replace, HistoryHandlingBehavior history_handling, UserNavigationInvolvement user_involvement)
|
||||
void finalize_a_same_document_navigation(GC::Ref<TraversableNavigable> traversable, GC::Ref<Navigable> target_navigable, GC::Ref<SessionHistoryEntry> target_entry, GC::Ptr<SessionHistoryEntry> entry_to_replace, HistoryHandlingBehavior history_handling, UserNavigationInvolvement user_involvement, GC::Ref<OnApplyHistoryStepComplete> on_complete)
|
||||
{
|
||||
// NOTE: This is not in the spec but we should not navigate destroyed navigable.
|
||||
if (target_navigable->has_been_destroyed())
|
||||
if (target_navigable->has_been_destroyed()) {
|
||||
on_complete->function()(HistoryStepResult::Applied);
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: 1. Assert: this is running on traversable's session history traversal queue.
|
||||
|
||||
@@ -1494,6 +1635,7 @@ void finalize_a_same_document_navigation(GC::Ref<TraversableNavigable> traversab
|
||||
*it = target_entry;
|
||||
}
|
||||
}
|
||||
on_complete->function()(HistoryStepResult::Applied);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1530,7 +1672,7 @@ void finalize_a_same_document_navigation(GC::Ref<TraversableNavigable> traversab
|
||||
}
|
||||
|
||||
// 6. Apply the push/replace history step targetStep to traversable given historyHandling and userInvolvement.
|
||||
traversable->apply_the_push_or_replace_history_step(*target_step, history_handling, user_involvement, TraversableNavigable::SynchronousNavigation::Yes);
|
||||
traversable->apply_the_push_or_replace_history_step(*target_step, history_handling, user_involvement, TraversableNavigable::SynchronousNavigation::Yes, on_complete);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/interaction.html#system-visibility-state
|
||||
|
||||
Reference in New Issue
Block a user