LibWeb: Keep stale child history tasks runnable
Child navigables can lose document-associated apply-history tasks when a parent page replaces or destroys the child document. Queue child tasks with no document association so they remain runnable, and share that choice for both activation and update-only continuations. Keep top-level work associated with the active document to preserve initial about:blank Window reuse. Also abandon a queued child fetch if its parent document is already gone before reading the parent's relevant settings object. This matches browser behavior for detached frame navigations and avoids resuming stale work against a discarded parent. The Twinings menu to Black Tea to Earl Grey product flow now reaches the product main content under Ladybird WebDriver. Existing navigation coverage and the full LibWeb text suite cover the local history cases.
This commit is contained in:
parent
b06955277a
commit
2d9db6c1f8
2 changed files with 39 additions and 7 deletions
|
|
@ -1189,7 +1189,18 @@ static void perform_navigation_params_fetch(JS::Realm& realm, GC::Ref<Navigation
|
|||
// 3. If navigable is not a top-level traversable, then:
|
||||
if (!state_holder->navigable->is_top_level_traversable()) {
|
||||
// 1. Let parentEnvironment be navigable's parent's active document's relevant settings object.
|
||||
auto& parent_environment = state_holder->navigable->parent()->active_document()->relevant_settings_object();
|
||||
auto parent = state_holder->navigable->parent();
|
||||
auto parent_document = parent ? parent->active_document() : nullptr;
|
||||
if (!parent || parent->has_been_destroyed() || !parent_document || parent_document->has_been_destroyed()) {
|
||||
// AD-HOC: A queued child navigation can resume after its parent document has been destroyed. The
|
||||
// specification assumes the parent environment is still available here, but browser engines
|
||||
// abandon this stale detached frame navigation instead of continuing it against a discarded
|
||||
// parent.
|
||||
state_holder->response = Fetch::Infrastructure::Response::network_error(realm.vm(), "Parent document is no longer active"_string);
|
||||
fetch_completion_steps->function()();
|
||||
return;
|
||||
}
|
||||
auto& parent_environment = parent_document->relevant_settings_object();
|
||||
|
||||
// 2. Set topLevelCreationURL to parentEnvironment's top-level creation URL.
|
||||
top_level_creation_url = parent_environment.top_level_creation_url;
|
||||
|
|
|
|||
|
|
@ -740,6 +740,19 @@ struct ChangingNavigableContinuationState : public JS::Cell {
|
|||
|
||||
GC_DEFINE_ALLOCATOR(ChangingNavigableContinuationState);
|
||||
|
||||
static void queue_apply_history_step_task(GC::Ref<Navigable> navigable, GC::Ptr<DOM::Document> top_level_document, GC::Ref<GC::Function<void()>> steps)
|
||||
{
|
||||
// AD-HOC: Queue top-level tasks with the active Document instead of using queue_global_task(active_window).
|
||||
// During initial about:blank Window reuse, active_window()->associated_document() can already be the
|
||||
// pending Document, but the apply-history task must run against the current active Document.
|
||||
//
|
||||
// Child navigables can destroy or deactivate their active Document before the queued task runs, causing
|
||||
// document-associated tasks to be dropped. Queue child tasks with a null Document so the task remains
|
||||
// runnable, and revalidate the child navigable inside the task.
|
||||
auto task_document = navigable->is_top_level_traversable() ? top_level_document : GC::Ptr<DOM::Document> {};
|
||||
queue_a_task(Task::Source::NavigationAndTraversal, nullptr, task_document, steps);
|
||||
}
|
||||
|
||||
class ApplyHistoryStepState : public GC::Cell {
|
||||
GC_CELL(ApplyHistoryStepState, GC::Cell);
|
||||
GC_DECLARE_ALLOCATOR(ApplyHistoryStepState);
|
||||
|
|
@ -947,12 +960,9 @@ void ApplyHistoryStepState::start()
|
|||
signal_progress();
|
||||
continue;
|
||||
}
|
||||
// AD-HOC: Queue with navigable's active Document instead of using queue_global_task(active_window).
|
||||
// During initial about:blank Window reuse, active_window()->associated_document() can already be
|
||||
// the pending Document. This activation task must stay runnable against the current active Document.
|
||||
queue_a_task(Task::Source::NavigationAndTraversal, nullptr, navigable->active_document(), GC::create_function(heap(), [this, navigable] {
|
||||
queue_apply_history_step_task(*navigable, navigable->active_document(), 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()) {
|
||||
if (navigable->has_been_destroyed() || !navigable->active_window() || !navigable->active_document()) {
|
||||
++m_completed_change_jobs;
|
||||
signal_progress();
|
||||
return;
|
||||
|
|
@ -1235,6 +1245,17 @@ void ApplyHistoryStepState::process_continuations()
|
|||
RefPtr<SessionHistoryEntry> const target_entry = continuation->target_entry;
|
||||
auto const displayed_document_id = continuation->displayed_document_id;
|
||||
auto after_potential_unload = GC::create_function(heap(), [this, navigable, update_only, target_entry, continuation, population_output, old_origin, displayed_document_id, script_history_length, script_history_index, entries_for_navigation_api = move(entries_for_navigation_api), navigation_type = m_navigation_type] {
|
||||
if (update_only || continuation->resolved_document.ptr() == continuation->displayed_document.ptr()) {
|
||||
// AD-HOC: Child navigable same-document/update-only tasks are queued without an associated Document so
|
||||
// they can survive the old active Document being deactivated. That also lets them run after the
|
||||
// child frame was destroyed or after a newer navigation claimed the frame. Browser engines let
|
||||
// the newer frame state win, so skip this stale continuation in that case.
|
||||
if (!changing_navigable_is_still_current(navigable, displayed_document_id)) {
|
||||
complete_change_job_without_applying(navigable);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (population_output)
|
||||
population_output->apply_to(*target_entry);
|
||||
|
||||
|
|
@ -1306,7 +1327,7 @@ void ApplyHistoryStepState::process_continuations()
|
|||
navigable->set_ongoing_navigation({}, m_navigation_api_abort_behavior);
|
||||
|
||||
// 2. Queue a global task on the navigation and traversal task source given navigable's active window to perform afterPotentialUnloads.
|
||||
queue_global_task(Task::Source::NavigationAndTraversal, *navigable->active_window(), after_potential_unload);
|
||||
queue_apply_history_step_task(*navigable, navigable->active_document(), after_potential_unload);
|
||||
}
|
||||
// AD-HOC: During navigable creation, the initial about:blank document can be
|
||||
// replaced by the container's initial navigation while applying the
|
||||
|
|
|
|||
Loading…
Reference in a new issue