/* * Copyright (c) 2018-2023, Andreas Kling * Copyright (c) 2021-2023, Linus Groh * Copyright (c) 2021-2023, Luke Wilde * Copyright (c) 2021-2023, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Web::DOM { JS_DEFINE_ALLOCATOR(Document); // https://html.spec.whatwg.org/multipage/origin.html#obtain-browsing-context-navigation static JS::NonnullGCPtr obtain_a_browsing_context_to_use_for_a_navigation_response( HTML::BrowsingContext& browsing_context, HTML::SandboxingFlagSet sandbox_flags, HTML::CrossOriginOpenerPolicy navigation_coop, HTML::CrossOriginOpenerPolicyEnforcementResult coop_enforcement_result) { // 1. If browsingContext is not a top-level browsing context, return browsingContext. if (!browsing_context.is_top_level()) return browsing_context; // 2. If coopEnforcementResult's needs a browsing context group switch is false, then: if (!coop_enforcement_result.needs_a_browsing_context_group_switch) { // 1. If coopEnforcementResult's would need a browsing context group switch due to report-only is true, if (coop_enforcement_result.would_need_a_browsing_context_group_switch_due_to_report_only) { // FIXME: set browsing context's virtual browsing context group ID to a new unique identifier. } // 2. Return browsingContext. return browsing_context; } // 3. Let newBrowsingContext be the first return value of creating a new top-level browsing context and document auto new_browsing_context = HTML::create_a_new_top_level_browsing_context_and_document(browsing_context.page()).release_value_but_fixme_should_propagate_errors().browsing_context; // FIXME: 4. If navigationCOOP's value is "same-origin-plurs-COEP", then set newBrowsingContext's group's // cross-origin isolation mode to either "logical" or "concrete". The choice of which is implementation-defined. // 5. If sandboxFlags is not empty, then: if (!is_empty(sandbox_flags)) { // 1. Assert navigationCOOP's value is "unsafe-none". VERIFY(navigation_coop.value == HTML::CrossOriginOpenerPolicyValue::UnsafeNone); // 2. Assert: newBrowsingContext's popup sandboxing flag set is empty. // 3. Set newBrowsingContext's popup sandboxing flag set to a clone of sandboxFlags. } // 6. Return newBrowsingContext. return new_browsing_context; } // https://html.spec.whatwg.org/multipage/browsing-the-web.html#initialise-the-document-object WebIDL::ExceptionOr> Document::create_and_initialize(Type type, String content_type, HTML::NavigationParams& navigation_params) { // 1. Let browsingContext be navigationParams's navigable's active browsing context. auto browsing_context = navigation_params.navigable->active_browsing_context(); // 2. Set browsingContext to the result of the obtaining a browsing context to use for a navigation response given browsingContext, navigationParams's final sandboxing flag set, // navigationParams's cross-origin opener policy, and navigationParams's COOP enforcement result. browsing_context = obtain_a_browsing_context_to_use_for_a_navigation_response( *browsing_context, navigation_params.final_sandboxing_flag_set, navigation_params.cross_origin_opener_policy, navigation_params.coop_enforcement_result); // FIXME: 3. Let permissionsPolicy be the result of creating a permissions policy from a response // given browsingContext, navigationParams's origin, and navigationParams's response. // 4. Let creationURL be navigationParams's response's URL. auto creation_url = navigation_params.response->url(); // 5. If navigationParams's request is non-null, then set creationURL to navigationParams's request's current URL. if (navigation_params.request) { creation_url = navigation_params.request->current_url(); } // 6. Let window be null. JS::GCPtr window; // 7. If browsingContext's active document's is initial about:blank is true, // and browsingContext's active document's origin is same origin-domain with navigationParams's origin, // then set window to browsingContext's active window. // FIXME: still_on_its_initial_about_blank_document() is not in the spec anymore. // However, replacing this with the spec-mandated is_initial_about_blank() results in the browsing context // holding an incorrect active document for the replace from initial about:blank to the real document. // See #22293 for more details. if (false && (browsing_context->active_document() && browsing_context->active_document()->origin().is_same_origin(navigation_params.origin))) { window = browsing_context->active_window(); } // 8. Otherwise: else { // FIXME: 1. Let oacHeader be the result of getting a structured field value given `Origin-Agent-Cluster` and "item" from response's header list. // FIXME: 2. Let requestsOAC be true if oacHeader is not null and oacHeader[0] is the boolean true; otherwise false. [[maybe_unused]] auto requests_oac = false; // FIXME: 3. If navigationParams's reserved environment is a non-secure context, then set requestsOAC to false. // FIXME: 4. Let agent be the result of obtaining a similar-origin window agent given navigationParams's origin, browsingContext's group, and requestsOAC. // 5. Let realm execution context be the result of creating a new JavaScript realm given agent and the following customizations: auto realm_execution_context = Bindings::create_a_new_javascript_realm( Bindings::main_thread_vm(), [&](JS::Realm& realm) -> JS::Object* { // - For the global object, create a new Window object. window = HTML::Window::create(realm); return window; }, [&](JS::Realm&) -> JS::Object* { // - For the global this binding, use browsingContext's WindowProxy object. return browsing_context->window_proxy(); }); // 6. Set window to the global object of realmExecutionContext's Realm component. window = verify_cast(realm_execution_context->realm->global_object()); // 7. Let topLevelCreationURL be creationURL. auto top_level_creation_url = creation_url; // 8. Let topLevelOrigin be navigationParams's origin. auto top_level_origin = navigation_params.origin; // 9. If navigable's container is not null, then: if (navigation_params.navigable->container()) { // 1. Let parentEnvironment be navigable's container's relevant settings object. auto& parent_environment = HTML::relevant_settings_object(*navigation_params.navigable->container()); // 2. Set topLevelCreationURL to parentEnvironment's top-level creation URL. top_level_creation_url = parent_environment.top_level_creation_url; // 3. Set topLevelOrigin to parentEnvironment's top-level origin. top_level_origin = parent_environment.top_level_origin; } // 9. Set up a window environment settings object with creationURL, realm execution context, // navigationParams's reserved environment, topLevelCreationURL, and topLevelOrigin. // FIXME: Why do we assume `creation_url` is non-empty here? Is this a spec bug? // FIXME: Why do we assume `top_level_creation_url` is non-empty here? Is this a spec bug? HTML::WindowEnvironmentSettingsObject::setup( browsing_context->page(), creation_url.value(), move(realm_execution_context), navigation_params.reserved_environment.visit( // FIXME: Environment is virtual. We *really* shouldn't be slicing it here [](Empty const&) -> Optional { return {}; }, [](HTML::Environment* env) -> Optional { if (env) return *env; return {}; }, [](JS::NonnullGCPtr) -> Optional { TODO(); }), top_level_creation_url.value(), top_level_origin); } // FIXME: 9. Let loadTimingInfo be a new document load timing info with its navigation start time set to navigationParams's response's timing info's start time. // 10. Let document be a new Document, with // type: type // content type: contentType // origin: navigationParams's origin // browsing context: browsingContext // policy container: navigationParams's policy container // FIXME: permissions policy: permissionsPolicy // active sandboxing flag set: navigationParams's final sandboxing flag set // FIXME: cross-origin opener policy: navigationParams's cross-origin opener policy // FIXME: load timing info: loadTimingInfo // FIXME: was created via cross-origin redirects: navigationParams's response's has cross-origin redirects // during-loading navigation ID for WebDriver BiDi: navigationParams's id // URL: creationURL // current document readiness: "loading" // about base URL: navigationParams's about base URL // FIXME: allow declarative shadow roots: true auto document = HTML::HTMLDocument::create(window->realm()); document->m_type = type; document->m_content_type = move(content_type); document->set_origin(navigation_params.origin); document->set_browsing_context(browsing_context); document->m_policy_container = navigation_params.policy_container; document->m_active_sandboxing_flag_set = navigation_params.final_sandboxing_flag_set; document->m_navigation_id = navigation_params.id; document->set_url(*creation_url); document->m_readiness = HTML::DocumentReadyState::Loading; document->m_about_base_url = navigation_params.about_base_url; document->m_window = window; // 11. Set window's associated Document to document. window->set_associated_document(*document); // FIXME: 12. Run CSP initialization for a Document given document. // 13. If navigationParams's request is non-null, then: if (navigation_params.request) { // 1. Set document's referrer to the empty string. document->m_referrer = String {}; // 2. Let referrer be navigationParams's request's referrer. auto const& referrer = navigation_params.request->referrer(); // 3. If referrer is a URL record, then set document's referrer to the serialization of referrer. if (referrer.has()) { document->m_referrer = MUST(String::from_byte_string(referrer.get().serialize())); } } // FIXME: 14: If navigationParams's fetch controller is not null, then: // FIXME: 15. Create the navigation timing entry for document, with navigationParams's response's timing info, redirectCount, navigationParams's navigation timing type, and // navigationParams's response's service worker timing info. // FIXME: 16. If navigationParams's response has a `Refresh` header, then: // FIXME: 17. If navigationParams's commit early hints is not null, then call navigationParams's commit early hints with document. // FIXME: 18. Process link headers given document, navigationParams's response, and "pre-media". // 19. Return document. return document; } WebIDL::ExceptionOr> Document::construct_impl(JS::Realm& realm) { return Document::create(realm); } JS::NonnullGCPtr Document::create(JS::Realm& realm, AK::URL const& url) { return realm.heap().allocate(realm, realm, url); } Document::Document(JS::Realm& realm, const AK::URL& url) : ParentNode(realm, *this, NodeType::DOCUMENT_NODE) , m_page(Bindings::host_defined_page(realm)) , m_style_computer(make(*this)) , m_url(url) { HTML::main_thread_event_loop().register_document({}, *this); m_style_update_timer = Core::Timer::create_single_shot(0, [this] { update_style(); }).release_value_but_fixme_should_propagate_errors(); m_layout_update_timer = Core::Timer::create_single_shot(0, [this] { update_layout(); }).release_value_but_fixme_should_propagate_errors(); } Document::~Document() { HTML::main_thread_event_loop().unregister_document({}, *this); } void Document::initialize(JS::Realm& realm) { Base::initialize(realm); set_prototype(&Bindings::ensure_web_prototype(realm, "Document"_fly_string)); m_selection = heap().allocate(realm, realm, *this); m_list_of_available_images = heap().allocate(realm); } // https://html.spec.whatwg.org/multipage/document-lifecycle.html#populate-with-html/head/body WebIDL::ExceptionOr Document::populate_with_html_head_and_body() { // 1. Let html be the result of creating an element given document, html, and the HTML namespace. auto html = TRY(DOM::create_element(*this, HTML::TagNames::html, Namespace::HTML)); // 2. Let head be the result of creating an element given document, head, and the HTML namespace. auto head = TRY(DOM::create_element(*this, HTML::TagNames::head, Namespace::HTML)); // 3. Let body be the result of creating an element given document, body, and the HTML namespace. auto body = TRY(DOM::create_element(*this, HTML::TagNames::body, Namespace::HTML)); // 4. Append html to document. TRY(append_child(html)); // 5. Append head to html. TRY(html->append_child(head)); // 6. Append body to html. TRY(html->append_child(body)); return {}; } void Document::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_page); visitor.visit(m_window); visitor.visit(m_layout_root); visitor.visit(m_style_sheets); visitor.visit(m_hovered_node); visitor.visit(m_inspected_node); visitor.visit(m_active_favicon); visitor.visit(m_focused_element); visitor.visit(m_active_element); visitor.visit(m_target_element); visitor.visit(m_implementation); visitor.visit(m_current_script); visitor.visit(m_associated_inert_template_document); visitor.visit(m_appropriate_template_contents_owner_document); visitor.visit(m_pending_parsing_blocking_script); visitor.visit(m_history); visitor.visit(m_browsing_context); visitor.visit(m_applets); visitor.visit(m_anchors); visitor.visit(m_images); visitor.visit(m_embeds); visitor.visit(m_links); visitor.visit(m_forms); visitor.visit(m_scripts); visitor.visit(m_all); visitor.visit(m_selection); visitor.visit(m_first_base_element_with_href_in_tree_order); visitor.visit(m_parser); visitor.visit(m_lazy_load_intersection_observer); visitor.visit(m_visual_viewport); visitor.visit(m_latest_entry); visitor.visit(m_default_timeline); for (auto& script : m_scripts_to_execute_when_parsing_has_finished) visitor.visit(script); for (auto& script : m_scripts_to_execute_in_order_as_soon_as_possible) visitor.visit(script); for (auto& script : m_scripts_to_execute_as_soon_as_possible) visitor.visit(script); for (auto& node_iterator : m_node_iterators) visitor.visit(node_iterator); for (auto& document_observer : m_document_observers) visitor.visit(document_observer); for (auto& target : m_pending_scroll_event_targets) visitor.visit(target); for (auto& target : m_pending_scrollend_event_targets) visitor.visit(target); for (auto& observer : m_intersection_observers) visitor.visit(observer); for (auto& image : m_shared_image_requests) visitor.visit(image.value); for (auto& timeline : m_associated_animation_timelines) visitor.visit(timeline); visitor.visit(m_list_of_available_images); for (auto* form_associated_element : m_form_associated_elements_with_form_attribute) visitor.visit(form_associated_element->form_associated_element_to_html_element()); } // https://w3c.github.io/selection-api/#dom-document-getselection JS::GCPtr Document::get_selection() const { // The method must return the selection associated with this if this has an associated browsing context, // and it must return null otherwise. if (!browsing_context()) return {}; return m_selection; } // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-write WebIDL::ExceptionOr Document::write(Vector const& strings) { StringBuilder builder; builder.join(""sv, strings); return run_the_document_write_steps(builder.string_view()); } // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-writeln WebIDL::ExceptionOr Document::writeln(Vector const& strings) { StringBuilder builder; builder.join(""sv, strings); builder.append("\n"sv); return run_the_document_write_steps(builder.string_view()); } // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps WebIDL::ExceptionOr Document::run_the_document_write_steps(StringView input) { // 1. If document is an XML document, then throw an "InvalidStateError" DOMException. if (m_type == Type::XML) return WebIDL::InvalidStateError::create(realm(), "write() called on XML document."_fly_string); // 2. If document's throw-on-dynamic-markup-insertion counter is greater than 0, then throw an "InvalidStateError" DOMException. if (m_throw_on_dynamic_markup_insertion_counter > 0) return WebIDL::InvalidStateError::create(realm(), "throw-on-dynamic-markup-insertion-counter greater than zero."_fly_string); // 3. If document's active parser was aborted is true, then return. if (m_active_parser_was_aborted) return {}; // 4. If the insertion point is undefined, then: if (!(m_parser && m_parser->tokenizer().is_insertion_point_defined())) { // 1. If document's unload counter is greater than 0 or document's ignore-destructive-writes counter is greater than 0, then return. if (m_unload_counter > 0 || m_ignore_destructive_writes_counter > 0) return {}; // 2. Run the document open steps with document. TRY(open()); } // 5. Insert input into the input stream just before the insertion point. m_parser->tokenizer().insert_input_at_insertion_point(input); // 6. If there is no pending parsing-blocking script, have the HTML parser process input, one code point at a time, processing resulting tokens as they are emitted, and stopping when the tokenizer reaches the insertion point or when the processing of the tokenizer is aborted by the tree construction stage (this can happen if a script end tag token is emitted by the tokenizer). if (!pending_parsing_blocking_script()) m_parser->run(); return {}; } // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-open WebIDL::ExceptionOr Document::open(Optional const&, Optional const&) { // 1. If document is an XML document, then throw an "InvalidStateError" DOMException exception. if (m_type == Type::XML) return WebIDL::InvalidStateError::create(realm(), "open() called on XML document."_fly_string); // 2. If document's throw-on-dynamic-markup-insertion counter is greater than 0, then throw an "InvalidStateError" DOMException. if (m_throw_on_dynamic_markup_insertion_counter > 0) return WebIDL::InvalidStateError::create(realm(), "throw-on-dynamic-markup-insertion-counter greater than zero."_fly_string); // FIXME: 3. Let entryDocument be the entry global object's associated Document. auto& entry_document = *this; // 4. If document's origin is not same origin to entryDocument's origin, then throw a "SecurityError" DOMException. if (origin() != entry_document.origin()) return WebIDL::SecurityError::create(realm(), "Document.origin() not the same as entryDocument's."_fly_string); // 5. If document has an active parser whose script nesting level is greater than 0, then return document. if (m_parser && m_parser->script_nesting_level() > 0) return this; // 6. Similarly, if document's unload counter is greater than 0, then return document. if (m_unload_counter > 0) return this; // 7. If document's active parser was aborted is true, then return document. if (m_active_parser_was_aborted) return this; // FIXME: 8. If document's browsing context is non-null and there is an existing attempt to navigate document's browsing context, then stop document loading given document. // FIXME: 9. For each shadow-including inclusive descendant node of document, erase all event listeners and handlers given node. // FIXME 10. If document is the associated Document of document's relevant global object, then erase all event listeners and handlers given document's relevant global object. // 11. Replace all with null within document, without firing any mutation events. replace_all(nullptr); // 12. If document is fully active, then: if (is_fully_active()) { // 1. Let newURL be a copy of entryDocument's URL. auto new_url = entry_document.url(); // 2. If entryDocument is not document, then set newURL's fragment to null. if (&entry_document != this) new_url.set_fragment({}); // FIXME: 3. Run the URL and history update steps with document and newURL. } // 13. Set document's is initial about:blank to false. set_is_initial_about_blank(false); // FIXME: 14. If document's iframe load in progress flag is set, then set document's mute iframe load flag. // 15. Set document to no-quirks mode. set_quirks_mode(QuirksMode::No); // 16. Create a new HTML parser and associate it with document. This is a script-created parser (meaning that it can be closed by the document.open() and document.close() methods, and that the tokenizer will wait for an explicit call to document.close() before emitting an end-of-file token). The encoding confidence is irrelevant. m_parser = HTML::HTMLParser::create_for_scripting(*this); // 17. Set the insertion point to point at just before the end of the input stream (which at this point will be empty). m_parser->tokenizer().update_insertion_point(); // 18. Update the current document readiness of document to "loading". update_readiness(HTML::DocumentReadyState::Loading); // 19. Return document. return this; } // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-open-window WebIDL::ExceptionOr> Document::open(StringView url, StringView name, StringView features) { // 1. If this is not fully active, then throw an "InvalidAccessError" DOMException exception. if (!is_fully_active()) return WebIDL::InvalidAccessError::create(realm(), "Cannot perform open on a document that isn't fully active."_fly_string); // 2. Return the result of running the window open steps with url, name, and features. return window().open_impl(url, name, features); } // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#closing-the-input-stream WebIDL::ExceptionOr Document::close() { // 1. If document is an XML document, then throw an "InvalidStateError" DOMException exception. if (m_type == Type::XML) return WebIDL::InvalidStateError::create(realm(), "close() called on XML document."_fly_string); // 2. If document's throw-on-dynamic-markup-insertion counter is greater than 0, then throw an "InvalidStateError" DOMException. if (m_throw_on_dynamic_markup_insertion_counter > 0) return WebIDL::InvalidStateError::create(realm(), "throw-on-dynamic-markup-insertion-counter greater than zero."_fly_string); // 3. If there is no script-created parser associated with the document, then return. if (!m_parser) return {}; // FIXME: 4. Insert an explicit "EOF" character at the end of the parser's input stream. m_parser->tokenizer().insert_eof(); // 5. If there is a pending parsing-blocking script, then return. if (pending_parsing_blocking_script()) return {}; // FIXME: 6. Run the tokenizer, processing resulting tokens as they are emitted, and stopping when the tokenizer reaches the explicit "EOF" character or spins the event loop. m_parser->run(); return {}; } HTML::Origin Document::origin() const { return m_origin; } void Document::set_origin(HTML::Origin const& origin) { m_origin = origin; } void Document::schedule_style_update() { if (m_style_update_timer->is_active()) return; m_style_update_timer->start(); } void Document::schedule_layout_update() { if (m_layout_update_timer->is_active()) return; m_layout_update_timer->start(); } bool Document::is_child_allowed(Node const& node) const { switch (node.type()) { case NodeType::DOCUMENT_NODE: case NodeType::TEXT_NODE: return false; case NodeType::COMMENT_NODE: return true; case NodeType::DOCUMENT_TYPE_NODE: return !first_child_of_type(); case NodeType::ELEMENT_NODE: return !first_child_of_type(); default: return false; } } Element* Document::document_element() { return first_child_of_type(); } Element const* Document::document_element() const { return first_child_of_type(); } // https://html.spec.whatwg.org/multipage/dom.html#the-html-element-2 HTML::HTMLHtmlElement* Document::html_element() { // The html element of a document is its document element, if it's an html element, and null otherwise. auto* html = document_element(); if (is(html)) return verify_cast(html); return nullptr; } // https://html.spec.whatwg.org/multipage/dom.html#the-head-element-2 HTML::HTMLHeadElement* Document::head() { // The head element of a document is the first head element that is a child of the html element, if there is one, // or null otherwise. auto* html = html_element(); if (!html) return nullptr; return html->first_child_of_type(); } // https://html.spec.whatwg.org/multipage/dom.html#the-title-element-2 JS::GCPtr Document::title_element() { // The title element of a document is the first title element in the document (in tree order), if there is one, or // null otherwise. JS::GCPtr title_element = nullptr; for_each_in_subtree_of_type([&](auto& title_element_in_tree) { title_element = title_element_in_tree; return IterationDecision::Break; }); return title_element; } HTML::HTMLElement* Document::body() { auto* html = html_element(); if (!html) return nullptr; auto* first_body = html->first_child_of_type(); if (first_body) return first_body; auto* first_frameset = html->first_child_of_type(); if (first_frameset) return first_frameset; return nullptr; } // https://html.spec.whatwg.org/multipage/dom.html#dom-document-body WebIDL::ExceptionOr Document::set_body(HTML::HTMLElement* new_body) { if (!is(new_body) && !is(new_body)) return WebIDL::HierarchyRequestError::create(realm(), "Invalid document body element, must be 'body' or 'frameset'"_fly_string); auto* existing_body = body(); if (existing_body) { (void)TRY(existing_body->parent()->replace_child(*new_body, *existing_body)); return {}; } auto* document_element = this->document_element(); if (!document_element) return WebIDL::HierarchyRequestError::create(realm(), "Missing document element"_fly_string); (void)TRY(document_element->append_child(*new_body)); return {}; } // https://html.spec.whatwg.org/multipage/dom.html#document.title String Document::title() const { String value; // 1. If the document element is an SVG svg element, then let value be the child text content of the first SVG title // element that is a child of the document element. if (auto const* document_element = this->document_element(); is(document_element)) { if (auto const* title_element = document_element->first_child_of_type()) value = title_element->child_text_content(); } // 2. Otherwise, let value be the child text content of the title element, or the empty string if the title element // is null. else if (auto title_element = this->title_element()) { value = title_element->text_content().value_or(String {}); } // 3. Strip and collapse ASCII whitespace in value. auto title = Infra::strip_and_collapse_whitespace(value).release_value_but_fixme_should_propagate_errors(); // 4. Return value. return title; } // https://html.spec.whatwg.org/multipage/dom.html#document.title WebIDL::ExceptionOr Document::set_title(String const& title) { auto* document_element = this->document_element(); // -> If the document element is an SVG svg element if (is(document_element)) { JS::GCPtr element; // 1. If there is an SVG title element that is a child of the document element, let element be the first such // element. if (auto* title_element = document_element->first_child_of_type()) { element = title_element; } // 2. Otherwise: else { // 1. Let element be the result of creating an element given the document element's node document, title, // and the SVG namespace. element = TRY(DOM::create_element(*this, HTML::TagNames::title, Namespace::SVG)); // 2. Insert element as the first child of the document element. document_element->insert_before(*element, nullptr); } // 3. String replace all with the given value within element. element->string_replace_all(title); } // -> If the document element is in the HTML namespace else if (document_element && document_element->namespace_uri() == Namespace::HTML) { auto title_element = this->title_element(); auto* head_element = this->head(); // 1. If the title element is null and the head element is null, then return. if (title_element == nullptr && head_element == nullptr) return {}; JS::GCPtr element; // 2. If the title element is non-null, let element be the title element. if (title_element) { element = title_element; } // 3. Otherwise: else { // 1. Let element be the result of creating an element given the document element's node document, title, // and the HTML namespace. element = TRY(DOM::create_element(*this, HTML::TagNames::title, Namespace::HTML)); // 2. Append element to the head element. TRY(head_element->append_child(*element)); } // 4. String replace all with the given value within element. element->string_replace_all(title); } // -> Otherwise else { // Do nothing. return {}; } if (browsing_context() == &page().top_level_browsing_context()) page().client().page_did_change_title(title.to_byte_string()); return {}; } void Document::tear_down_layout_tree() { m_layout_root = nullptr; m_paintable = nullptr; } Color Document::background_color() const { // CSS2 says we should use the HTML element's background color unless it's transparent... if (auto* html_element = this->html_element(); html_element && html_element->layout_node()) { auto color = html_element->layout_node()->computed_values().background_color(); if (color.alpha()) return color; } // ...in which case we use the BODY element's background color. if (auto* body_element = body(); body_element && body_element->layout_node()) { auto color = body_element->layout_node()->computed_values().background_color(); return color; } // By default, the document is transparent. // The outermost canvas is colored by the PageHost. return Color::Transparent; } Vector const* Document::background_layers() const { auto* body_element = body(); if (!body_element) return {}; auto* body_layout_node = body_element->layout_node(); if (!body_layout_node) return {}; return &body_layout_node->background_layers(); } void Document::update_base_element(Badge) { JS::GCPtr base_element; for_each_in_subtree_of_type([&base_element](HTML::HTMLBaseElement const& base_element_in_tree) { if (base_element_in_tree.has_attribute(HTML::AttributeNames::href)) { base_element = &base_element_in_tree; return IterationDecision::Break; } return IterationDecision::Continue; }); m_first_base_element_with_href_in_tree_order = base_element; } JS::GCPtr Document::first_base_element_with_href_in_tree_order() const { return m_first_base_element_with_href_in_tree_order; } // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url AK::URL Document::fallback_base_url() const { // 1. If document is an iframe srcdoc document, then: if (HTML::url_matches_about_srcdoc(m_url)) { // 1. Assert: document's about base URL is non-null. VERIFY(m_about_base_url.has_value()); // 2. Return document's about base URL. return m_about_base_url.value(); } // 2. If document's URL matches about:blank and document's about base URL is non-null, then return document's about base URL. if (HTML::url_matches_about_blank(m_url) && m_about_base_url.has_value()) return m_about_base_url.value(); // 3. Return document's URL. return m_url; } // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#document-base-url AK::URL Document::base_url() const { // 1. If there is no base element that has an href attribute in the Document, then return the Document's fallback base URL. auto base_element = first_base_element_with_href_in_tree_order(); if (!base_element) return fallback_base_url(); // 2. Otherwise, return the frozen base URL of the first base element in the Document that has an href attribute, in tree order. return base_element->frozen_base_url(); } // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#parse-a-url AK::URL Document::parse_url(StringView url) const { // FIXME: Pass in document's character encoding. return base_url().complete_url(url); } void Document::set_needs_layout() { if (m_needs_layout) return; m_needs_layout = true; schedule_layout_update(); } void Document::force_layout() { tear_down_layout_tree(); update_layout(); } void Document::invalidate_layout() { tear_down_layout_tree(); schedule_layout_update(); } static void propagate_overflow_to_viewport(Element& root_element, Layout::Viewport& viewport) { // https://drafts.csswg.org/css-overflow-3/#overflow-propagation // UAs must apply the overflow-* values set on the root element to the viewport // when the root element’s display value is not none. auto* overflow_origin_node = root_element.layout_node(); auto& viewport_computed_values = viewport.mutable_computed_values(); // However, when the root element is an [HTML] html element (including XML syntax for HTML) // whose overflow value is visible (in both axes), and that element has as a child // a body element whose display value is also not none, // user agents must instead apply the overflow-* values of the first such child element to the viewport. if (root_element.is_html_html_element()) { auto* root_element_layout_node = root_element.layout_node(); auto& root_element_computed_values = root_element_layout_node->mutable_computed_values(); if (root_element_computed_values.overflow_x() == CSS::Overflow::Visible && root_element_computed_values.overflow_y() == CSS::Overflow::Visible) { auto* body_element = root_element.first_child_of_type(); if (body_element && body_element->layout_node()) overflow_origin_node = body_element->layout_node(); } } // NOTE: This is where we assign the chosen overflow values to the viewport. auto& overflow_origin_computed_values = overflow_origin_node->mutable_computed_values(); viewport_computed_values.set_overflow_x(overflow_origin_computed_values.overflow_x()); viewport_computed_values.set_overflow_y(overflow_origin_computed_values.overflow_y()); // The element from which the value is propagated must then have a used overflow value of visible. overflow_origin_computed_values.set_overflow_x(CSS::Overflow::Visible); overflow_origin_computed_values.set_overflow_y(CSS::Overflow::Visible); } void Document::update_layout() { if (!is_active()) return; // NOTE: If our parent document needs a relayout, we must do that *first*. // This is necessary as the parent layout may cause our viewport to change. if (navigable() && navigable()->container()) navigable()->container()->document().update_layout(); update_style(); if (!m_needs_layout && m_layout_root) return; // NOTE: If this is a document hosting