diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index e3e1a1f2a654a80..2b0910daca9c255 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -2696,14 +2696,15 @@ void Document::destroy() { page()->client().page_did_destroy_document(*this); - // NOTE: Abort needs to happen before destory. There is currently bug in the spec: https://github.com/whatwg/html/issues/9148 - // 4. Abort document. + // FIXME: 1. Assert: this is running as part of a task queued on document's relevant agent's event loop. + + // 2. Abort document. abort(); - // 2. Set document's salvageable state to false. + // 3. Set document's salvageable state to false. m_salvageable = false; - // 3. Run any unloading document cleanup steps for document that are defined by this specification and other applicable specifications. + // 4. Run any unloading document cleanup steps for document that are defined by this specification and other applicable specifications. run_unloading_cleanup_steps(); // 5. Remove any tasks whose document is document from any task queue (without running those tasks). @@ -2714,14 +2715,6 @@ void Document::destroy() // 6. Set document's browsing context to null. m_browsing_context = nullptr; - // When a frame element stops being an active frame element, the user agent must destroy a child navigable given the element. - // A frame element is said to be an active frame element when it is in a document tree and its node document's browsing context is non-null. - for (auto& navigable_container : HTML::NavigableContainer::all_instances()) { - if (&navigable_container->document() == this) { - navigable_container->destroy_the_child_navigable(); - } - } - // 7. Set document's node navigable's active session history entry's document state's document to null. if (navigable()) { navigable()->active_session_history_entry()->document_state->set_document(nullptr); @@ -2732,23 +2725,38 @@ void Document::destroy() // FIXME: 9. For each workletGlobalScope in document's worklet global scopes, terminate workletGlobalScope. } +void Document::destroy_document_and_its_descendants(JS::NonnullGCPtr document) +{ + // 1. Let childNavigables be document's child navigables. + auto child_navigables = document->document_tree_child_navigables(); + + // 2. Let numberDestroyed be 0. + size_t number_destroyed = 0; + + // 3. For each childNavigable of childNavigable's, queue a global task on the navigation and traversal task source + // given childNavigable's active window to perform the following steps: + for (auto& child_navigable : child_navigables) { + HTML::queue_global_task(HTML::Task::Source::NavigationAndTraversal, *child_navigable->active_window(), [&] { + // 1. Destroy childNavigable's active document. + child_navigable->active_document()->destroy(); + + // 2. Increment numberDestroyed. + number_destroyed++; + }); + } + + // 4. Wait until numberDestroyed equals childNavigable's size. + HTML::main_thread_event_loop().spin_until([&] { + return number_destroyed == child_navigables.size(); + }); +} + // https://html.spec.whatwg.org/multipage/browsing-the-web.html#abort-a-document void Document::abort() { // 1. Abort the active documents of each of document's descendant navigables. // If this results in any of those Document objects having their salvageable state set to false, // then set document's salvageable state to false also. - for (auto navigable : descendant_navigables()) { - if (auto document = navigable->active_document()) { - // NOTE: This is not in the spec but we need to abort ongoing navigations in all descendandt navigables. - // See https://github.com/whatwg/html/issues/9711 - navigable->set_ongoing_navigation({}); - - document->abort(); - if (!document->m_salvageable) - m_salvageable = false; - } - } // FIXME: 2. Cancel any instances of the fetch algorithm in the context of document, // discarding any tasks queued for them, and discarding any further data received from the network for them. @@ -2756,11 +2764,11 @@ void Document::abort() // or any queued tasks or any network data getting discarded, // then set document's salvageable state to false. - // 3. If document's navigation id is non-null, then: + // 3. If document's during-loading navigation ID for WebDriver BiDi is non-null, then: if (m_navigation_id.has_value()) { - // 1. FIXME: Invoke WebDriver BiDi navigation aborted with document's browsing context, - // and new WebDriver BiDi navigation status whose whose id is document's navigation id, - // status is "canceled", and url is document's URL. + // FIXME: 1. Invoke WebDriver BiDi navigation aborted with document's browsing context, and new WebDriver BiDi navigation + // status whose whose id is document's during-loading navigation ID for WebDriver BiDi, status is "canceled", + // and url is document's URL. // 2. Set document's navigation id to null. m_navigation_id = {}; @@ -2779,6 +2787,31 @@ void Document::abort() } } +void Document::abort_document_and_its_descendants(JS::NonnullGCPtr document) +{ + // 1. Assert: this is running as part of a task queued on document's relevant agent's event loop. + + // 2. Let descendantNavigables be document's descendant navigables. + auto descendant_navigables = document->descendant_navigables(); + + // 3. For each descendantNavigable of descendantNavigables, queue a global task on the navigation and traversal task + // source given descendantNavigable's active window to perform the following steps: + for (auto& descendant_navigable : descendant_navigables) { + HTML::queue_global_task(HTML::Task::Source::NavigationAndTraversal, *descendant_navigable->active_window(), [descendant_navigable] { + // 1. Abort descendantNavigable's active document. + descendant_navigable->active_document()->abort(); + + // 2. If descendantNavigable's active document's salvageable is false, then set document's salvageable to false. + if (!descendant_navigable->active_document()->m_salvageable) { + descendant_navigable->active_document()->set_salvageable(false); + } + }); + } + + // 4. Abort document. + document->abort(); +} + // https://html.spec.whatwg.org/multipage/dom.html#active-parser JS::GCPtr Document::active_parser() { @@ -2879,6 +2912,43 @@ void Document::unload(JS::GCPtr) did_stop_being_active_document_in_navigable(); } +void Document::unload_a_document_and_its_descendants(JS::NonnullGCPtr document, JS::GCPtr new_document, JS::SafeFunction after_all_unloads) +{ + // FIXME: 1. Assert: this is running within document's node navigable's traversable navigable's session history traversal queue. + + // 2. Let childNavigables be document's child navigables. + auto child_navigables = document->document_tree_child_navigables(); + + // 3. Let numberUnloaded be 0. + size_t number_unloaded = 0; + + // 4. For each childNavigable of childNavigable's, queue a global task on the navigation and traversal task source given childNavigable's active window to perform the following steps: + for (auto& child_navigable : child_navigables) { + HTML::queue_global_task(HTML::Task::Source::NavigationAndTraversal, *child_navigable->active_window(), [child_navigable, &number_unloaded] { + // 1. Unload childNavigable's active document. + child_navigable->active_document()->unload(); + + // 2. Increment numberUnloaded. + number_unloaded++; + }); + } + + // 5. Wait until numberUnloaded equals childNavigable's size. + HTML::main_thread_event_loop().spin_until([&] { + return number_unloaded == child_navigables.size(); + }); + + // 6. Queue a global task on the navigation and traversal task source given document's relevant global object to perform the following steps: + HTML::queue_global_task(HTML::Task::Source::NavigationAndTraversal, relevant_global_object(*document), [document, after_all_unloads = move(after_all_unloads)] { + // 1. Unload document, passing along newDocument if it is not null. + document->unload(new_document); + + // 2. If afterAllUnloads was given, then run it. + if (after_all_unloads) + after_all_unloads(); + }); +} + // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#allowed-to-use bool Document::is_allowed_to_use_feature(PolicyControlledFeature feature) const { diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h index a6b904d007d4a06..e67093385d17e74 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.h +++ b/Userland/Libraries/LibWeb/DOM/Document.h @@ -484,6 +484,10 @@ class Document // https://html.spec.whatwg.org/multipage/document-lifecycle.html#unload-a-document void unload(JS::GCPtr new_document = nullptr); + static void destroy_document_and_its_descendants(JS::NonnullGCPtr); + static void abort_document_and_its_descendants(JS::NonnullGCPtr); + static void unload_a_document_and_its_descendants(JS::NonnullGCPtr document, JS::GCPtr new_document, JS::SafeFunction after_all_unloads); + // https://html.spec.whatwg.org/multipage/dom.html#active-parser JS::GCPtr active_parser(); diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.cpp b/Userland/Libraries/LibWeb/HTML/Navigable.cpp index 8788790f5644a47..e6f2255a3fd8fb5 100644 --- a/Userland/Libraries/LibWeb/HTML/Navigable.cpp +++ b/Userland/Libraries/LibWeb/HTML/Navigable.cpp @@ -1251,10 +1251,10 @@ WebIDL::ExceptionOr Navigable::navigate(NavigateParams params) return; } - // 3. Queue a global task on the navigation and traversal task source given navigable's active window to abort navigable's active document. + // 3. Queue a global task on the navigation and traversal task source given navigable's active window to abort a document and its descendants given navigable's active document. queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), [this] { VERIFY(this->active_document()); - this->active_document()->abort(); + DOM::Document::abort_document_and_its_descendants(*this->active_document()); }); // 4. Let documentState be a new document state with diff --git a/Userland/Libraries/LibWeb/HTML/NavigableContainer.cpp b/Userland/Libraries/LibWeb/HTML/NavigableContainer.cpp index 75acd9e8bdcb040..0b0997f63c3666c 100644 --- a/Userland/Libraries/LibWeb/HTML/NavigableContainer.cpp +++ b/Userland/Libraries/LibWeb/HTML/NavigableContainer.cpp @@ -262,8 +262,8 @@ void NavigableContainer::destroy_the_child_navigable() // 3. Set container's content navigable to null. m_content_navigable = nullptr; - // 4. Destroy navigable's active document. - navigable->active_document()->destroy(); + // 4. Destroy a document and its descendants given navigable's active document. + DOM::Document::destroy_document_and_its_descendants(*navigable->active_document()); // 5. Let parentDocState be container's node navigable's active session history entry's document state. auto parent_doc_state = this->navigable()->active_session_history_entry()->document_state; diff --git a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp index cfdec1ed13c2956..d0f7cac103227dc 100644 --- a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp +++ b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp @@ -416,43 +416,51 @@ void TraversableNavigable::apply_the_history_step(int step, Optionalactive_window(), [&, target_entry, navigable, displayed_document, update_only = changing_navigable_continuation.update_only, script_history_length, script_history_index] { - // NOTE: This check is not in the spec but we should not continue navigation if navigable has been destroyed. - if (navigable->has_been_destroyed()) - return; + // FIXME: 10. Let entriesForNavigationAPI be the result of getting session history entries for the navigation API given navigable and targetStep. - // 1. If changingNavigableContinuation's update-only is false, then: - if (!update_only) { - // 1. If targetEntry's document does not equal displayedDocument, then: - if (target_entry->document_state->document().ptr() != displayed_document.ptr()) { - // 1. Unload displayedDocument given targetEntry's document. - displayed_document->unload(target_entry->document_state->document()); - - // 2. For each childNavigable of displayedDocument's descendant navigables, queue a global task on the navigation and traversal task source given - // childNavigable's active window to unload childNavigable's active document. - for (auto child_navigable : displayed_document->descendant_navigables()) { - queue_global_task(Task::Source::NavigationAndTraversal, *navigable->active_window(), [child_navigable] { - child_navigable->active_document()->unload(); - }); - } - } + // 11. If changingNavigableContinuation's update-only is false, and targetEntry's document does not equal displayedDocument, then unload a document + // and its descendants given displayedDocument, targetEntry's document, and afterPotentialUnloads. + auto update_only = changing_navigable_continuation.update_only; - // 3. Activate history entry targetEntry for navigable. + JS::SafeFunction after_potential_unloads = [update_only, navigable, target_entry, &completed_change_jobs, script_history_length, script_history_index, displayed_document] { + // 1. If changingNavigableContinuation's update-only is false, then activate history entry targetEntry for navigable. + if (!update_only) { navigable->activate_history_entry(*target_entry); } - // FIXME: 2. If targetEntry's document is not equal to displayedDocument, then queue a global task on the navigation and traversal task source given targetEntry's document's - // relevant global object to perform the following step. Otherwise, continue onward to perform the following step within the currently-queued task. + // FIXME: 2. If navigable is not traversable, and targetEntry is not navigable's current session history entry, and targetEntry's document + // state's origin is the same as navigable's current session history entry's document state's origin, then fire a traverse navigate + // event given targetEntry and userInvolvementForNavigateEvents. - // 3. Update document for history step application given targetEntry's document, targetEntry, changingNavigableContinuation's update-only, scriptHistoryLength, and - // scriptHistoryIndex and entriesForNavigationAPI. - // FIXME: Pass entriesForNavigationAPI - target_entry->document_state->document()->update_for_history_step_application(*target_entry, update_only, script_history_length, script_history_index); + // 3. Let updateDocument be an algorithm step which performs update document for history step application given targetEntry's document, targetEntry, + // changingNavigableContinuation's update-only, scriptHistoryLength, scriptHistoryIndex, and entriesForNavigationAPI. + auto update_document = [target_entry, update_only, script_history_length, script_history_index] { + target_entry->document_state->document()->update_for_history_step_application(*target_entry, update_only, script_history_length, script_history_index); + }; - // 4. Increment completedChangeJobs. + // 4. If targetEntry's document is equal to displayedDocument, then perform updateDocument. + if (target_entry->document_state->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_state->document()), move(update_document)); + } + + // 6. Increment completedChangeJobs. completed_change_jobs++; - }); + }; + + // 10. If changingNavigableContinuation's update-only is false, and targetEntry's document does not equal displayedDocument, then unload a document and its + // descendants given displayedDocument, targetEntry's document, and afterPotentialUnloads. + if (!update_only && target_entry->document_state->document().ptr() != displayed_document.ptr()) { + DOM::Document::unload_a_document_and_its_descendants(*displayed_document.ptr(), target_entry->document_state->document(), move(after_potential_unloads)); + } else { + // Otherwise, 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_unloads = move(after_potential_unloads)] { + after_potential_unloads(); + }); + } } // FIXME: 15. Let totalNonchangingJobs be the size of nonchangingNavigablesThatStillNeedUpdates. @@ -591,9 +599,9 @@ void TraversableNavigable::destroy_top_level_traversable() // 1. Let document be historyEntry's document. auto document = history_entry->document_state->document(); - // 2. If document is not null, then destroy document. + // 2. If document is not null, then destroy document and its descendants given document. if (document) - document->destroy(); + DOM::Document::destroy_a_document_and_its_descendants(*document); } // 3. Remove browsingContext.