diff --git a/Tests/LibWeb/Text/expected/title.txt b/Tests/LibWeb/Text/expected/title.txt new file mode 100644 index 0000000000..1aa8cfe090 --- /dev/null +++ b/Tests/LibWeb/Text/expected/title.txt @@ -0,0 +1,14 @@ +1: "" +2a: 0 +2b: 1 +2c: "This is a title!" +2d: "This is a title!" +3: "" +4a: 3 +4b: "" +4c: "" +4d: "" +4e: 3 +4f: "This is another title!" +4g: "" +4h: "" diff --git a/Tests/LibWeb/Text/input/title.html b/Tests/LibWeb/Text/input/title.html new file mode 100644 index 0000000000..8af05865f5 --- /dev/null +++ b/Tests/LibWeb/Text/input/title.html @@ -0,0 +1,42 @@ + + diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 25c3a9ec46..3b4988af16 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -79,6 +79,7 @@ #include #include #include +#include #include #include #include @@ -680,52 +681,87 @@ WebIDL::ExceptionOr Document::set_body(HTML::HTMLElement* new_body) return {}; } +// https://html.spec.whatwg.org/multipage/dom.html#document.title DeprecatedString Document::title() const { - auto* head_element = head(); - if (!head_element) - return {}; + auto value = DeprecatedString::empty(); - auto* title_element = head_element->first_child_of_type(); - if (!title_element) - return {}; - - auto raw_title = title_element->text_content(); - - StringBuilder builder; - bool last_was_space = false; - for (auto code_point : Utf8View(raw_title)) { - if (is_ascii_space(code_point)) { - last_was_space = true; - } else { - if (last_was_space && !builder.is_empty()) - builder.append(' '); - builder.append_code_point(code_point); - last_was_space = false; - } + // 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(); document_element && is(document_element)) { + // FIXME: Implement the SVG title element and get its child text content. } - return builder.to_deprecated_string(); + + // 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(); + } + + // 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.to_deprecated_string(); } -void Document::set_title(DeprecatedString const& title) +// https://html.spec.whatwg.org/multipage/dom.html#document.title +WebIDL::ExceptionOr Document::set_title(DeprecatedString const& title) { - auto* head_element = const_cast(head()); - if (!head_element) - return; + auto* document_element = this->document_element(); - JS::GCPtr title_element = head_element->first_child_of_type(); - if (!title_element) { - title_element = &static_cast(*DOM::create_element(*this, HTML::TagNames::title, Namespace::HTML).release_value_but_fixme_should_propagate_errors()); - MUST(head_element->append_child(*title_element)); + // -> If the document element is an SVG svg element + if (is(document_element)) { + // FIXME: 1. If there is an SVG title element that is a child of the document element, let element be the first such + // element. + // FIXME: 2. Otherwise: + // 1. Let element be the result of creating an element given the document element's node document, title, + // and the SVG namespace. + // 2. Insert element as the first child of the document element. + // FIXME: 3. String replace all with the given value within element. } - title_element->remove_all_children(true); - MUST(title_element->append_child(heap().allocate(realm(), *this, title).release_allocated_value_but_fixme_should_propagate_errors())); + // -> If the document element is in the HTML namespace + else if (document_element && document_element->namespace_() == 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 (auto* page = this->page()) { if (browsing_context() == &page->top_level_browsing_context()) page->client().page_did_change_title(title); } + + return {}; } void Document::tear_down_layout_tree() diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h index 8cb36f505f..da6c63997d 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.h +++ b/Userland/Libraries/LibWeb/DOM/Document.h @@ -173,7 +173,7 @@ public: WebIDL::ExceptionOr set_body(HTML::HTMLElement* new_body); DeprecatedString title() const; - void set_title(DeprecatedString const&); + WebIDL::ExceptionOr set_title(DeprecatedString const&); HTML::BrowsingContext* browsing_context() { return m_browsing_context.ptr(); } HTML::BrowsingContext const* browsing_context() const { return m_browsing_context.ptr(); }