", LIBXML_NOERROR);
+var_dump($dom->body?->nodeName);
+
+echo "--- After body removal ---\n";
+
+$dom->body->remove();
+var_dump($dom->body?->nodeName);
+
+echo "--- body in no namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("", "body"));
+var_dump($dom->body?->nodeName);
+$tmp->remove();
+
+echo "--- frameset in no namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("", "frameset"));
+var_dump($dom->body?->nodeName);
+$tmp->remove();
+
+echo "--- body in right namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "body"));
+var_dump($dom->body?->nodeName);
+$tmp->remove();
+
+echo "--- frameset in right namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "frameset"));
+var_dump($dom->body?->nodeName);
+$tmp->remove();
+
+echo "--- prefixed body in right namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix:body"));
+var_dump($dom->body?->nodeName);
+$tmp->remove();
+
+echo "--- prefixed frameset in right namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix:frameset"));
+var_dump($dom->body?->nodeName);
+$tmp->remove();
+
+echo "--- multiple body-like elements in right namespace ---\n";
+
+$tmp1 = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix1:body"));
+var_dump($dom->body?->nodeName);
+$tmp2 = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix2:frameset"));
+var_dump($dom->body?->nodeName);
+$tmp1->remove();
+var_dump($dom->body?->nodeName);
+$tmp2->remove();
+var_dump($dom->body?->nodeName);
+
+echo "--- html element in no namespace ---\n";
+
+$dom = Dom\XMLDocument::createFromString(<<
+
+
+XML);
+var_dump($dom->body);
+
+?>
+--EXPECT--
+--- From parsing ---
+string(4) "BODY"
+--- After body removal ---
+NULL
+--- body in no namespace ---
+NULL
+--- frameset in no namespace ---
+NULL
+--- body in right namespace ---
+string(4) "BODY"
+--- frameset in right namespace ---
+string(8) "FRAMESET"
+--- prefixed body in right namespace ---
+string(11) "PREFIX:BODY"
+--- prefixed frameset in right namespace ---
+string(15) "PREFIX:FRAMESET"
+--- multiple body-like elements in right namespace ---
+string(12) "PREFIX1:BODY"
+string(12) "PREFIX1:BODY"
+string(16) "PREFIX2:FRAMESET"
+NULL
+--- html element in no namespace ---
+NULL
diff --git a/ext/dom/tests/modern/html/interactions/Document_body_setter.phpt b/ext/dom/tests/modern/html/interactions/Document_body_setter.phpt
new file mode 100644
index 0000000000000..87cbb9b9c9e6e
--- /dev/null
+++ b/ext/dom/tests/modern/html/interactions/Document_body_setter.phpt
@@ -0,0 +1,48 @@
+--TEST--
+Test DOM\Document::$body setter
+--EXTENSIONS--
+dom
+--FILE--
+foo', LIBXML_NOERROR);
+$dom->body = $dom->body;
+var_dump($dom->body?->nodeName);
+
+echo "--- Add body when there is no body yet ---\n";
+$dom = DOM\HTMLDocument::createFromString('
foo
', LIBXML_NOERROR);
+$dom->body->remove();
+$dom->body = $dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix:body");
+var_dump($dom->body?->nodeName);
+
+echo "--- Replace old body with new body ---\n";
+$dom = DOM\HTMLDocument::createFromString('
foo
', LIBXML_NOERROR);
+$dom->body = $dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix:body");
+var_dump($dom->body?->nodeName);
+
+echo "--- Replace old body with new body, while still having a reference to the old body ---\n";
+$dom = DOM\HTMLDocument::createFromString('
foo
', LIBXML_NOERROR);
+$old = $dom->body;
+$dom->body = $dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix:body");
+var_dump($dom->body?->nodeName);
+var_dump($old->nodeName);
+
+echo "--- Special note from the DOM spec ---\n";
+$dom = DOM\XMLDocument::createFromString('');
+$dom->body = $dom->createElementNS("http://www.w3.org/1999/xhtml", "body");
+var_dump($dom->body?->nodeName);
+
+?>
+--EXPECT--
+--- Replace body with itself ---
+string(4) "BODY"
+--- Add body when there is no body yet ---
+string(11) "PREFIX:BODY"
+--- Replace old body with new body ---
+string(11) "PREFIX:BODY"
+--- Replace old body with new body, while still having a reference to the old body ---
+string(11) "PREFIX:BODY"
+string(4) "BODY"
+--- Special note from the DOM spec ---
+NULL
diff --git a/ext/dom/tests/modern/html/interactions/Document_body_setter_errors.phpt b/ext/dom/tests/modern/html/interactions/Document_body_setter_errors.phpt
new file mode 100644
index 0000000000000..1f934fcff890d
--- /dev/null
+++ b/ext/dom/tests/modern/html/interactions/Document_body_setter_errors.phpt
@@ -0,0 +1,61 @@
+--TEST--
+Test DOM\Document::$body setter errors
+--EXTENSIONS--
+dom
+--FILE--
+foo', LIBXML_NOERROR);
+ var_dump($dom->body?->nodeName);
+ try {
+ $dom->body = $cb($dom);
+ } catch (Throwable $e) {
+ echo $e->getMessage(), "\n";
+ }
+ var_dump($dom->body?->nodeName);
+}
+
+echo "--- Set body to NULL ---\n";
+testNormalReplace(fn ($dom) => NULL);
+
+echo "--- Wrong element tag in right namespace ---\n";
+testNormalReplace(fn ($dom) => $dom->createElementNS("http://www.w3.org/1999/xhtml", "foo"));
+
+echo "--- Right element tag in wrong namespace ---\n";
+testNormalReplace(fn ($dom) => $dom->createElementNS("urn:a", "body"));
+
+echo "--- Right element tag in no namespace ---\n";
+testNormalReplace(fn ($dom) => $dom->createElementNS("", "frameset"));
+
+echo "--- Set body without document element ---\n";
+$dom = DOM\XMLDocument::createEmpty();
+try {
+ $dom->body = $dom->createElementNS("http://www.w3.org/1999/xhtml", "body");
+} catch (DOMException $e) {
+ echo $e->getMessage(), "\n";
+}
+var_dump($dom->body?->nodeName);
+
+?>
+--EXPECT--
+--- Set body to NULL ---
+string(4) "BODY"
+The new body must either be a body or a frameset tag
+string(4) "BODY"
+--- Wrong element tag in right namespace ---
+string(4) "BODY"
+The new body must either be a body or a frameset tag
+string(4) "BODY"
+--- Right element tag in wrong namespace ---
+string(4) "BODY"
+Cannot assign Dom\Element to property Dom\Document::$body of type ?Dom\HTMLElement
+string(4) "BODY"
+--- Right element tag in no namespace ---
+string(4) "BODY"
+Cannot assign Dom\Element to property Dom\Document::$body of type ?Dom\HTMLElement
+string(4) "BODY"
+--- Set body without document element ---
+A body can only be set if there is a document element
+NULL
diff --git a/ext/dom/tests/modern/html/interactions/Document_head.phpt b/ext/dom/tests/modern/html/interactions/Document_head.phpt
new file mode 100644
index 0000000000000..cc34945951405
--- /dev/null
+++ b/ext/dom/tests/modern/html/interactions/Document_head.phpt
@@ -0,0 +1,74 @@
+--TEST--
+Test Dom\Document::$head
+--EXTENSIONS--
+dom
+--FILE--
+foo", LIBXML_NOERROR);
+var_dump($dom->head?->nodeName);
+
+echo "--- After head removal ---\n";
+
+$dom->head->remove();
+var_dump($dom->head?->nodeName);
+
+echo "--- head in no namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("", "head"));
+var_dump($dom->head?->nodeName);
+$tmp->remove();
+
+echo "--- head in right namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "head"));
+var_dump($dom->head?->nodeName);
+$tmp->remove();
+
+echo "--- prefixed head in right namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix:head"));
+var_dump($dom->head?->nodeName);
+$tmp->remove();
+
+echo "--- multiple head elements in right namespace ---\n";
+
+$tmp1 = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix1:head"));
+var_dump($dom->head?->nodeName);
+$tmp2 = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix2:head"));
+var_dump($dom->head?->nodeName);
+$tmp1->remove();
+var_dump($dom->head?->nodeName);
+$tmp2->remove();
+var_dump($dom->head?->nodeName);
+
+echo "--- html element in no namespace ---\n";
+
+$dom = Dom\XMLDocument::createFromString(<<
+
+
+XML);
+var_dump($dom->head);
+
+?>
+--EXPECT--
+--- From parsing ---
+string(4) "HEAD"
+--- After head removal ---
+NULL
+--- head in no namespace ---
+NULL
+--- head in right namespace ---
+string(4) "HEAD"
+--- prefixed head in right namespace ---
+string(11) "PREFIX:HEAD"
+--- multiple head elements in right namespace ---
+string(12) "PREFIX1:HEAD"
+string(12) "PREFIX1:HEAD"
+string(12) "PREFIX2:HEAD"
+NULL
+--- html element in no namespace ---
+NULL
diff --git a/ext/dom/tests/modern/html/interactions/HTMLDocument_registerNodeClass_03.phpt b/ext/dom/tests/modern/html/interactions/HTMLDocument_registerNodeClass_03.phpt
index 7a4d2d32797dc..6616eb5d11465 100644
--- a/ext/dom/tests/modern/html/interactions/HTMLDocument_registerNodeClass_03.phpt
+++ b/ext/dom/tests/modern/html/interactions/HTMLDocument_registerNodeClass_03.phpt
@@ -5,7 +5,7 @@ dom
--FILE--
foo", LIBXML_NOERROR);
-$dom->registerNodeClass("Dom\\Element", "Custom");
+$dom->registerNodeClass("Dom\\HTMLElement", "Custom");
var_dump($dom->getElementsByTagName('div')[0]->reverseTagName());
diff --git a/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_01.phpt b/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_01.phpt
index 2931aebab28f5..5051c3f9aabf6 100644
--- a/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_01.phpt
+++ b/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_01.phpt
@@ -5,10 +5,10 @@ dom
--FILE--
foo", LIBXML_NOERROR);
-$dom->registerNodeClass("Dom\\Element", "MyElement");
+$dom->registerNodeClass("Dom\\HTMLElement", "MyElement");
// Destroy reference to the DOM
$child = $dom->documentElement;
@@ -23,7 +23,7 @@ var_dump(get_class($dom->getElementsByTagName("p")->item(0)));
?>
--EXPECT--
-object(Dom\HTMLDocument)#1 (25) {
+object(Dom\HTMLDocument)#1 (28) {
["implementation"]=>
string(22) "(object value omitted)"
["URL"]=>
@@ -46,6 +46,12 @@ object(Dom\HTMLDocument)#1 (25) {
string(22) "(object value omitted)"
["childElementCount"]=>
int(1)
+ ["body"]=>
+ string(22) "(object value omitted)"
+ ["head"]=>
+ string(22) "(object value omitted)"
+ ["title"]=>
+ string(0) ""
["nodeType"]=>
int(13)
["nodeName"]=>
diff --git a/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_02.phpt b/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_02.phpt
index c356cf7ba9215..b160c72f0a54f 100644
--- a/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_02.phpt
+++ b/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_02.phpt
@@ -5,10 +5,10 @@ dom
--FILE--
foo", LIBXML_NOERROR);
-$dom->registerNodeClass("Dom\\Element", "MyElement");
+$dom->registerNodeClass("Dom\\HTMLElement", "MyElement");
$child = $dom->documentElement->appendChild($dom->createElement('html'));
// Destroy reference to the DOM
@@ -23,7 +23,7 @@ var_dump(get_class($dom->getElementsByTagName("p")->item(0)));
?>
--EXPECT--
-object(Dom\HTMLDocument)#1 (25) {
+object(Dom\HTMLDocument)#1 (28) {
["implementation"]=>
string(22) "(object value omitted)"
["URL"]=>
@@ -46,6 +46,12 @@ object(Dom\HTMLDocument)#1 (25) {
string(22) "(object value omitted)"
["childElementCount"]=>
int(1)
+ ["body"]=>
+ string(22) "(object value omitted)"
+ ["head"]=>
+ string(22) "(object value omitted)"
+ ["title"]=>
+ string(0) ""
["nodeType"]=>
int(13)
["nodeName"]=>
diff --git a/ext/dom/tests/modern/spec/Document_implementation_createDocument.phpt b/ext/dom/tests/modern/spec/Document_implementation_createDocument.phpt
index 5384cf6331f76..ae49a6a494c9a 100644
--- a/ext/dom/tests/modern/spec/Document_implementation_createDocument.phpt
+++ b/ext/dom/tests/modern/spec/Document_implementation_createDocument.phpt
@@ -37,7 +37,7 @@ echo $dom->implementation->createDocument(null, "", $dtd)->saveXml(), "\n";
?>
--EXPECT--
--- (null, "") ---
-object(Dom\XMLDocument)#3 (29) {
+object(Dom\XMLDocument)#3 (32) {
["xmlEncoding"]=>
string(5) "UTF-8"
["xmlStandalone"]=>
@@ -68,6 +68,12 @@ object(Dom\XMLDocument)#3 (29) {
NULL
["childElementCount"]=>
int(0)
+ ["body"]=>
+ NULL
+ ["head"]=>
+ NULL
+ ["title"]=>
+ string(0) ""
["nodeType"]=>
int(9)
["nodeName"]=>
diff --git a/ext/dom/tests/modern/spec/Element_prefix_readonly.phpt b/ext/dom/tests/modern/spec/Element_prefix_readonly.phpt
index 7ebc3efaadc2b..78625fcb6f7d3 100644
--- a/ext/dom/tests/modern/spec/Element_prefix_readonly.phpt
+++ b/ext/dom/tests/modern/spec/Element_prefix_readonly.phpt
@@ -14,5 +14,5 @@ try {
echo $dom->saveXml();
?>
--EXPECT--
-Cannot modify readonly property Dom\Element::$prefix
+Cannot modify readonly property Dom\HTMLElement::$prefix
diff --git a/ext/dom/tests/modern/xml/XMLDocument_debug.phpt b/ext/dom/tests/modern/xml/XMLDocument_debug.phpt
index c9ae2afa95fdf..e2d6ebffe89cd 100644
--- a/ext/dom/tests/modern/xml/XMLDocument_debug.phpt
+++ b/ext/dom/tests/modern/xml/XMLDocument_debug.phpt
@@ -10,7 +10,7 @@ var_dump($dom);
?>
--EXPECT--
-object(Dom\XMLDocument)#1 (29) {
+object(Dom\XMLDocument)#1 (32) {
["xmlEncoding"]=>
string(5) "UTF-8"
["xmlStandalone"]=>
@@ -41,6 +41,12 @@ object(Dom\XMLDocument)#1 (29) {
NULL
["childElementCount"]=>
int(0)
+ ["body"]=>
+ NULL
+ ["head"]=>
+ NULL
+ ["title"]=>
+ string(0) ""
["nodeType"]=>
int(9)
["nodeName"]=>
diff --git a/ext/dom/tests/modern/xml/XMLDocument_fromEmptyDocument_02.phpt b/ext/dom/tests/modern/xml/XMLDocument_fromEmptyDocument_02.phpt
index a804394bc8441..62d64a05f9b2a 100644
--- a/ext/dom/tests/modern/xml/XMLDocument_fromEmptyDocument_02.phpt
+++ b/ext/dom/tests/modern/xml/XMLDocument_fromEmptyDocument_02.phpt
@@ -10,7 +10,7 @@ var_dump($dom);
?>
--EXPECT--
-object(Dom\XMLDocument)#1 (29) {
+object(Dom\XMLDocument)#1 (32) {
["xmlEncoding"]=>
string(5) "UTF-8"
["xmlStandalone"]=>
@@ -41,6 +41,12 @@ object(Dom\XMLDocument)#1 (29) {
NULL
["childElementCount"]=>
int(0)
+ ["body"]=>
+ NULL
+ ["head"]=>
+ NULL
+ ["title"]=>
+ string(0) ""
["nodeType"]=>
int(9)
["nodeName"]=>
diff --git a/ext/dom/tests/modern/xml/XMLDocument_node_ownerDocument_for_XML.phpt b/ext/dom/tests/modern/xml/XMLDocument_node_ownerDocument_for_XML.phpt
index 946f9495114a7..e18c43f05ae82 100644
--- a/ext/dom/tests/modern/xml/XMLDocument_node_ownerDocument_for_XML.phpt
+++ b/ext/dom/tests/modern/xml/XMLDocument_node_ownerDocument_for_XML.phpt
@@ -13,7 +13,7 @@ var_dump($element->ownerDocument);
?>
--EXPECTF--
-object(Dom\XMLDocument)#1 (29) {
+object(Dom\XMLDocument)#1 (32) {
["xmlEncoding"]=>
string(5) "UTF-8"
["xmlStandalone"]=>
@@ -44,6 +44,12 @@ object(Dom\XMLDocument)#1 (29) {
string(22) "(object value omitted)"
["childElementCount"]=>
int(1)
+ ["body"]=>
+ NULL
+ ["head"]=>
+ NULL
+ ["title"]=>
+ string(0) ""
["nodeType"]=>
int(9)
["nodeName"]=>