/* * Copyright (c) 2018-2023, Andreas Kling * Copyright (c) 2021-2023, Linus Groh * Copyright (c) 2021-2023, Luke Wilde * Copyright (c) 2021-2023, Sam Atkins * Copyright (c) 2024, Matthew Olsson * * 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 #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 const& 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, URL::URL const& url) { return realm.heap().allocate(realm, realm, url); } Document::Document(JS::Realm& realm, const URL::URL& url) : ParentNode(realm, *this, NodeType::DOCUMENT_NODE) , m_page(Bindings::host_defined_page(realm)) , m_style_computer(make(*this)) , m_url(url) { m_legacy_platform_object_flags = PlatformObject::LegacyPlatformObjectFlags { .supports_named_properties = true, .has_legacy_override_built_ins_interface_extended_attribute = true, }; HTML::main_thread_event_loop().register_document({}, *this); } Document::~Document() { HTML::main_thread_event_loop().unregister_document({}, *this); } void Document::initialize(JS::Realm& realm) { Base::initialize(realm); WEB_SET_PROTOTYPE_FOR_INTERFACE(Document); 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& observer : m_resize_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()); for (auto& element : m_potentially_named_elements) visitor.visit(element); for (auto& event : m_pending_animation_event_queue) { visitor.visit(event.event); visitor.visit(event.target); } visitor.visit(m_adopted_style_sheets); for (auto& shadow_root : m_shadow_roots) visitor.visit(shadow_root); for (auto& element : m_top_layer_elements) visitor.visit(element); for (auto& element : m_top_layer_pending_removals) visitor.visit(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(HTML::HTMLTokenizer::StopAtInsertionPoint::Yes); 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() { // NOTE: Update of the style is a step in HTML event loop processing. HTML::main_thread_event_loop().schedule(); } void Document::schedule_layout_update() { // NOTE: Update of the layout is a step in HTML event loop processing. HTML::main_thread_event_loop().schedule(); } 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 URL::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 URL::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 URL::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. auto navigable = this->navigable(); 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