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(); }