From 92deba7197dc705109423945d52518de6d4d52c7 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 20 Sep 2022 21:44:42 +0200 Subject: [PATCH] LibWeb: Implement Document/BrowsingContext hookup according to spec We now implement the browsing context's "set active document" algorithm from the spec, as well as the "discard" algorithm for browsing contexts and documents. --- Userland/Libraries/LibWeb/DOM/Document.cpp | 115 +++++++++++++++--- Userland/Libraries/LibWeb/DOM/Document.h | 20 ++- .../Libraries/LibWeb/HTML/BrowsingContext.cpp | 83 ++++++++----- .../Libraries/LibWeb/HTML/BrowsingContext.h | 7 +- .../LibWeb/HTML/BrowsingContextContainer.cpp | 11 -- .../LibWeb/HTML/BrowsingContextContainer.h | 1 - .../LibWeb/HTML/HTMLIFrameElement.cpp | 16 ++- .../LibWeb/HTML/HTMLObjectElement.cpp | 4 +- .../Libraries/LibWeb/Loader/FrameLoader.cpp | 2 +- 9 files changed, 189 insertions(+), 70 deletions(-) diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 5ba5e43dfa..267404145e 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -651,20 +651,6 @@ void Document::set_title(String const& title) } } -void Document::attach_to_browsing_context(Badge, HTML::BrowsingContext& browsing_context) -{ - m_browsing_context = browsing_context; - - update_the_visibility_state(browsing_context.system_visibility_state()); -} - -void Document::detach_from_browsing_context(Badge, HTML::BrowsingContext& browsing_context) -{ - VERIFY(&browsing_context == m_browsing_context); - tear_down_layout_tree(); - m_browsing_context = nullptr; -} - void Document::tear_down_layout_tree() { if (!m_layout_root) @@ -1605,6 +1591,11 @@ String Document::visibility_state() const VERIFY_NOT_REACHED(); } +void Document::set_visibility_state(Badge, HTML::VisibilityState visibility_state) +{ + m_visibility_state = visibility_state; +} + // https://html.spec.whatwg.org/multipage/interaction.html#update-the-visibility-state void Document::update_the_visibility_state(HTML::VisibilityState visibility_state) { @@ -2021,4 +2012,100 @@ Vector> Document::list_of_descendant_browsi return list; } +// https://html.spec.whatwg.org/multipage/window-object.html#discard-a-document +void Document::discard() +{ + // 1. Set document's salvageable state to false. + m_salvageable = false; + + // FIXME: 2. Run any unloading document cleanup steps for document that are defined by this specification and other applicable specifications. + + // 3. Abort document. + abort(); + + // 4. Remove any tasks associated with document in any task source, without running those tasks. + HTML::main_thread_event_loop().task_queue().remove_tasks_matching([this](auto& task) { + return task.document() == this; + }); + + // 5. Discard all the child browsing contexts of document. + if (browsing_context()) { + browsing_context()->for_each_child([](HTML::BrowsingContext& child_browsing_context) { + child_browsing_context.discard(); + }); + } + + // FIXME: 6. For each session history entry entry whose document is equal to document, set entry's document to null. + + // 7. Set document's browsing context to null. + tear_down_layout_tree(); + m_browsing_context = nullptr; + + // FIXME: 8. Remove document from the owner set of each WorkerGlobalScope object whose set contains document. + + // FIXME: 9. For each workletGlobalScope in document's worklet global scopes, terminate workletGlobalScope. +} + +// https://html.spec.whatwg.org/multipage/browsing-the-web.html#abort-a-document +void Document::abort() +{ + // 1. Abort the active documents of every child browsing context. + // 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. + if (browsing_context()) { + browsing_context()->for_each_child([this](HTML::BrowsingContext& child_browsing_context) { + if (auto* child_document = child_browsing_context.active_document()) { + child_document->abort(); + if (!child_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. + // If this resulted in any instances of the fetch algorithm being canceled + // 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: + 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. + + // 2. Set document's navigation id to null. + m_navigation_id = {}; + } + + // 4. If document has an active parser, then: + if (auto parser = active_parser()) { + // 1. Set document's active parser was aborted to true. + m_active_parser_was_aborted = true; + + // 2. Abort that parser. + parser->abort(); + + // 3. Set document's salvageable state to false. + m_salvageable = false; + } +} + +// https://html.spec.whatwg.org/multipage/dom.html#active-parser +RefPtr Document::active_parser() +{ + if (!m_parser) + return nullptr; + + if (m_parser->aborted() || m_parser->stopped()) + return nullptr; + + return m_parser; +} + +void Document::set_browsing_context(HTML::BrowsingContext* browsing_context) +{ + m_browsing_context = browsing_context; +} + } diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h index 230d810f71..bf54165835 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.h +++ b/Userland/Libraries/LibWeb/DOM/Document.h @@ -135,12 +135,11 @@ public: String title() const; void set_title(String const&); - void attach_to_browsing_context(Badge, HTML::BrowsingContext&); - void detach_from_browsing_context(Badge, HTML::BrowsingContext&); - HTML::BrowsingContext* browsing_context() { return m_browsing_context.ptr(); } HTML::BrowsingContext const* browsing_context() const { return m_browsing_context.ptr(); } + void set_browsing_context(HTML::BrowsingContext*); + Page* page(); Page const* page() const; @@ -322,6 +321,9 @@ public: // https://html.spec.whatwg.org/multipage/interaction.html#update-the-visibility-state void update_the_visibility_state(HTML::VisibilityState); + // NOTE: This does not fire any events, unlike update_the_visibility_state(). + void set_visibility_state(Badge, HTML::VisibilityState); + void run_the_resize_steps(); void run_the_scroll_steps(); @@ -386,6 +388,15 @@ public: // https://html.spec.whatwg.org/multipage/browsers.html#list-of-the-descendant-browsing-contexts Vector> list_of_descendant_browsing_contexts() const; + // https://html.spec.whatwg.org/multipage/window-object.html#discard-a-document + void discard(); + + // https://html.spec.whatwg.org/multipage/browsing-the-web.html#abort-a-document + void abort(); + + // https://html.spec.whatwg.org/multipage/dom.html#active-parser + RefPtr active_parser(); + protected: virtual void visit_edges(Cell::Visitor&) override; @@ -476,6 +487,9 @@ private: size_t m_number_of_things_delaying_the_load_event { 0 }; + // https://html.spec.whatwg.org/multipage/browsing-the-web.html#concept-document-salvageable + bool m_salvageable { true }; + // https://html.spec.whatwg.org/#page-showing bool m_page_showing { false }; diff --git a/Userland/Libraries/LibWeb/HTML/BrowsingContext.cpp b/Userland/Libraries/LibWeb/HTML/BrowsingContext.cpp index f25f38c466..0e5cfd934b 100644 --- a/Userland/Libraries/LibWeb/HTML/BrowsingContext.cpp +++ b/Userland/Libraries/LibWeb/HTML/BrowsingContext.cpp @@ -207,7 +207,7 @@ NonnullRefPtr BrowsingContext::create_a_new_browsing_context(Pa document->append_child(html_node); // 19. Set the active document of browsingContext to document. - browsing_context->m_active_document = JS::make_handle(*document); + browsing_context->set_active_document(*document); // 20. If browsingContext's creator URL is non-null, then set document's referrer to the serialization of it. if (browsing_context->m_creator_url.has_value()) { @@ -227,9 +227,6 @@ NonnullRefPtr BrowsingContext::create_a_new_browsing_context(Pa .original_source_browsing_context = {}, }); - // Non-standard: - document->attach_to_browsing_context({}, browsing_context); - // 23. Completely finish loading document. document->completely_finish_loading(); @@ -286,36 +283,29 @@ bool BrowsingContext::is_focused_context() const return m_page && &m_page->focused_context() == this; } -void BrowsingContext::set_active_document(DOM::Document* document) +// https://html.spec.whatwg.org/multipage/browsers.html#set-the-active-document +void BrowsingContext::set_active_document(JS::NonnullGCPtr document) { - if (m_active_document.ptr() == document) - return; + // 1. Let window be document's relevant global object. + auto& window = verify_cast(relevant_global_object(document)); - m_cursor_position = {}; + // 2. Set document's visibility state to browsingContext's top-level browsing context's system visibility state. + document->set_visibility_state({}, top_level_browsing_context().system_visibility_state()); - if (m_active_document) - m_active_document->detach_from_browsing_context({}, *this); + // 3. Set browsingContext's active window to window. + m_active_window = window; - // https://html.spec.whatwg.org/multipage/browsing-the-web.html#resetBCName - // FIXME: The rest of set_active_document does not follow the spec very closely, this just implements the - // relevant steps for resetting the browsing context name and should be updated closer to the spec once - // the other parts of history handling/navigating are implemented - // 3. If newDocument's origin is not same origin with the current entry's document's origin, then: - if (!document || !m_active_document || !document->origin().is_same_origin(m_active_document->origin())) { - // 3. If the browsing context is a top-level browsing context, but not an auxiliary browsing context - // whose disowned is false, then set the browsing context's name to the empty string. - // FIXME: this is not checking the second part of the condition yet - if (is_top_level()) - m_name = String::empty(); - } + // 4. Set window's associated Document to document. + window.set_associated_document(document); - m_active_document = JS::make_handle(document); + // 5. Set window's relevant settings object's execution ready flag. + relevant_settings_object(window).execution_ready = true; - if (m_active_document) { - m_active_document->attach_to_browsing_context({}, *this); - if (m_page && is_top_level()) - m_page->client().page_did_change_title(m_active_document->title()); - } + // AD-HOC: + document->set_browsing_context(this); + + if (m_page && is_top_level()) + m_page->client().page_did_change_title(document->title()); } void BrowsingContext::set_viewport_rect(Gfx::IntRect const& rect) @@ -792,22 +782,26 @@ bool BrowsingContext::still_on_its_initial_about_blank_document() const DOM::Document const* BrowsingContext::active_document() const { - return m_active_document.cell(); + if (!m_active_window) + return nullptr; + return &m_active_window->associated_document(); } DOM::Document* BrowsingContext::active_document() { - return m_active_document.cell(); + if (!m_active_window) + return nullptr; + return &m_active_window->associated_document(); } HTML::Window* BrowsingContext::active_window() { - return m_active_document ? &m_active_document->window() : nullptr; + return m_active_window; } HTML::Window const* BrowsingContext::active_window() const { - return m_active_document ? &m_active_document->window() : nullptr; + return m_active_window; } void BrowsingContext::scroll_offset_did_change() @@ -1087,7 +1081,7 @@ DOM::ExceptionOr BrowsingContext::traverse_the_history(size_t entry_index, } // 4. Set the active document of the browsing context to newDocument. - set_active_document(new_document); + set_active_document(*new_document); // 5. If entry's browsing context name is not null, then: if (entry->browsing_context_name.has_value()) { @@ -1299,4 +1293,27 @@ void BrowsingContext::set_system_visibility_state(VisibilityState visibility_sta }); } +// https://html.spec.whatwg.org/multipage/window-object.html#a-browsing-context-is-discarded +void BrowsingContext::discard() +{ + // 1. Discard all Document objects for all the entries in browsingContext's session history. + for (auto& entry : m_session_history) { + if (entry.document) + entry.document->discard(); + } + + // AD-HOC: + // FIXME: This should be in the session history! + if (auto* document = active_document()) + document->discard(); + + // 2. If browsingContext is a top-level browsing context, then remove browsingContext. + if (is_top_level()) + remove(); + + // AD-HOC: + if (parent()) + parent()->remove_child(*this); +} + } diff --git a/Userland/Libraries/LibWeb/HTML/BrowsingContext.h b/Userland/Libraries/LibWeb/HTML/BrowsingContext.h index 05c986f636..f0db2a5f59 100644 --- a/Userland/Libraries/LibWeb/HTML/BrowsingContext.h +++ b/Userland/Libraries/LibWeb/HTML/BrowsingContext.h @@ -47,7 +47,7 @@ public: DOM::Document const* active_document() const; DOM::Document* active_document(); - void set_active_document(DOM::Document*); + void set_active_document(JS::NonnullGCPtr); HTML::Window* active_window(); HTML::Window const* active_window() const; @@ -165,6 +165,9 @@ public: VisibilityState system_visibility_state() const; void set_system_visibility_state(VisibilityState); + // https://html.spec.whatwg.org/multipage/window-object.html#a-browsing-context-is-discarded + void discard(); + private: explicit BrowsingContext(Page&, HTML::BrowsingContextContainer*); @@ -195,7 +198,7 @@ private: Optional m_creator_origin; WeakPtr m_container; - JS::Handle m_active_document; + JS::Handle m_active_window; Gfx::IntSize m_size; Gfx::IntPoint m_viewport_scroll_offset; diff --git a/Userland/Libraries/LibWeb/HTML/BrowsingContextContainer.cpp b/Userland/Libraries/LibWeb/HTML/BrowsingContextContainer.cpp index 48ed8132a4..fecd1788c1 100644 --- a/Userland/Libraries/LibWeb/HTML/BrowsingContextContainer.cpp +++ b/Userland/Libraries/LibWeb/HTML/BrowsingContextContainer.cpp @@ -55,17 +55,6 @@ void BrowsingContextContainer::create_new_nested_browsing_context() m_nested_browsing_context->set_name(name); } -// https://html.spec.whatwg.org/multipage/window-object.html#a-browsing-context-is-discarded -void BrowsingContextContainer::discard_nested_browsing_context() -{ - // 1. Discard all Document objects for all the entries in browsingContext's session history. - if (m_nested_browsing_context && m_nested_browsing_context->parent()) - m_nested_browsing_context->parent()->remove_child(*m_nested_browsing_context); - - // 2. If browsingContext is a top-level browsing context, then remove browsingContext. - // NOTE: We skip this here because this is by definition a nested browsing context, not top-level. -} - // https://html.spec.whatwg.org/multipage/browsers.html#concept-bcc-content-document const DOM::Document* BrowsingContextContainer::content_document() const { diff --git a/Userland/Libraries/LibWeb/HTML/BrowsingContextContainer.h b/Userland/Libraries/LibWeb/HTML/BrowsingContextContainer.h index f1b14c246a..8dbe88eb33 100644 --- a/Userland/Libraries/LibWeb/HTML/BrowsingContextContainer.h +++ b/Userland/Libraries/LibWeb/HTML/BrowsingContextContainer.h @@ -38,7 +38,6 @@ protected: void navigate_an_iframe_or_frame(Fetch::Infrastructure::Request); void create_new_nested_browsing_context(); - void discard_nested_browsing_context(); RefPtr m_nested_browsing_context; diff --git a/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp index 771e6fa05e..34fda75a53 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp @@ -95,7 +95,13 @@ void HTMLIFrameElement::process_the_iframe_attributes(bool initial_insertion) void HTMLIFrameElement::removed_from(DOM::Node* node) { HTMLElement::removed_from(node); - discard_nested_browsing_context(); + + // When an iframe element is removed from a document, the user agent must discard the element's nested browsing context, + // if it is not null, and then set the element's nested browsing context to null. + if (m_nested_browsing_context) { + m_nested_browsing_context->discard(); + m_nested_browsing_context = nullptr; + } } void HTMLIFrameElement::load_src(String const& value) @@ -123,8 +129,12 @@ void HTMLIFrameElement::load_src(String const& value) // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#iframe-load-event-steps void run_iframe_load_event_steps(HTML::HTMLIFrameElement& element) { - // 1. Assert: element's nested browsing context is not null. - VERIFY(element.nested_browsing_context()); + // FIXME: 1. Assert: element's nested browsing context is not null. + if (!element.nested_browsing_context()) { + // FIXME: For some reason, we sometimes end up here in the middle of SunSpider. + dbgln("FIXME: run_iframe_load_event_steps called with null nested browsing context"); + return; + } // 2. Let childDocument be the active document of element's nested browsing context. [[maybe_unused]] auto* child_document = element.nested_browsing_context()->active_document(); diff --git a/Userland/Libraries/LibWeb/HTML/HTMLObjectElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLObjectElement.cpp index 42133c30db..d791560daf 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLObjectElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLObjectElement.cpp @@ -235,7 +235,7 @@ void HTMLObjectElement::run_object_representation_handler_steps(Optional else if (resource_type.has_value() && resource_type->starts_with("image/"sv)) { // If the object element's nested browsing context is non-null, then it must be discarded and then set to null. if (m_nested_browsing_context) { - discard_nested_browsing_context(); + m_nested_browsing_context->discard(); m_nested_browsing_context = nullptr; } @@ -276,7 +276,7 @@ void HTMLObjectElement::run_object_representation_fallback_steps() { // 6. Fallback: The object element represents the element's children, ignoring any leading param element children. This is the element's fallback content. If the element has an instantiated plugin, then unload it. If the element's nested browsing context is non-null, then it must be discarded and then set to null. if (m_nested_browsing_context) { - discard_nested_browsing_context(); + m_nested_browsing_context->discard(); m_nested_browsing_context = nullptr; } diff --git a/Userland/Libraries/LibWeb/Loader/FrameLoader.cpp b/Userland/Libraries/LibWeb/Loader/FrameLoader.cpp index f757ddd81e..65f438af53 100644 --- a/Userland/Libraries/LibWeb/Loader/FrameLoader.cpp +++ b/Userland/Libraries/LibWeb/Loader/FrameLoader.cpp @@ -275,7 +275,7 @@ void FrameLoader::load_html(StringView html, const AK::URL& url) auto parser = HTML::HTMLParser::create(document, html, "utf-8"); parser->run(url); - browsing_context().set_active_document(&parser->document()); + browsing_context().set_active_document(parser->document()); } static String s_error_page_url = "file:///res/html/error.html";