diff --git a/Tests/LibWeb/Text/expected/DOM/Text-wholeText.txt b/Tests/LibWeb/Text/expected/DOM/Text-wholeText.txt
new file mode 100644
index 00000000000..7a5faa33fe8
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/DOM/Text-wholeText.txt
@@ -0,0 +1,6 @@
+Text node A with no siblings: A
+Text node B with previous sibling A: AB
+Text node B with previous sibling A and next sibling C: ABC
+CDATA section D with no siblings: D
+CDATA section E with previous sibling D: DE
+CDATA section E with previous sibling D and next sibling F: DEF
\ No newline at end of file
diff --git a/Tests/LibWeb/Text/input/DOM/Text-wholeText.html b/Tests/LibWeb/Text/input/DOM/Text-wholeText.html
new file mode 100644
index 00000000000..88362152ec3
--- /dev/null
+++ b/Tests/LibWeb/Text/input/DOM/Text-wholeText.html
@@ -0,0 +1,36 @@
+
+
+
diff --git a/Userland/Libraries/LibWeb/DOM/Text.cpp b/Userland/Libraries/LibWeb/DOM/Text.cpp
index d78d65dec32..58413a15f92 100644
--- a/Userland/Libraries/LibWeb/DOM/Text.cpp
+++ b/Userland/Libraries/LibWeb/DOM/Text.cpp
@@ -117,4 +117,36 @@ WebIDL::ExceptionOr> Text::split_text(size_t offset)
return new_node;
}
+// https://dom.spec.whatwg.org/#dom-text-wholetext
+String Text::whole_text()
+{
+ // https://dom.spec.whatwg.org/#contiguous-text-nodes
+ // The contiguous Text nodes of a node node are node, node’s previous sibling Text node, if any, and its contiguous
+ // Text nodes, and node’s next sibling Text node, if any, and its contiguous Text nodes, avoiding any duplicates.
+ Vector nodes;
+
+ nodes.append(this);
+
+ auto* current_node = previous_sibling();
+ while (current_node && (current_node->is_text() || current_node->is_cdata_section())) {
+ nodes.append(static_cast(current_node));
+ current_node = current_node->previous_sibling();
+ }
+
+ // Reverse nodes so they are in tree order
+ nodes.reverse();
+
+ current_node = next_sibling();
+ while (current_node && (current_node->is_text() || current_node->is_cdata_section())) {
+ nodes.append(static_cast(current_node));
+ current_node = current_node->next_sibling();
+ }
+
+ StringBuilder builder;
+ for (auto const& text_node : nodes)
+ builder.append(text_node->data());
+
+ return MUST(builder.to_string());
+}
+
}
diff --git a/Userland/Libraries/LibWeb/DOM/Text.h b/Userland/Libraries/LibWeb/DOM/Text.h
index 9d531dd5e82..4549f18f57c 100644
--- a/Userland/Libraries/LibWeb/DOM/Text.h
+++ b/Userland/Libraries/LibWeb/DOM/Text.h
@@ -43,6 +43,7 @@ public:
EditableTextNodeOwner* editable_text_node_owner();
WebIDL::ExceptionOr> split_text(size_t offset);
+ String whole_text();
bool is_password_input() const { return m_is_password_input; }
void set_is_password_input(Badge, bool b) { m_is_password_input = b; }
diff --git a/Userland/Libraries/LibWeb/DOM/Text.idl b/Userland/Libraries/LibWeb/DOM/Text.idl
index 4dc15d10227..059dcdf7fe7 100644
--- a/Userland/Libraries/LibWeb/DOM/Text.idl
+++ b/Userland/Libraries/LibWeb/DOM/Text.idl
@@ -8,7 +8,7 @@ interface Text : CharacterData {
constructor(optional DOMString data = "");
[NewObject] Text splitText(unsigned long offset);
- [FIXME] readonly attribute DOMString wholeText;
+ readonly attribute DOMString wholeText;
};
Text includes Slottable;