Kaynağa Gözat

LibWeb: Make Node::is_text() return true for CDATASection nodes

CDATASection inherits from Text, and so it was incorrect for them to
claim not to be Text nodes.

This fixes at least two WPT subtests. :^)

It also exposed a bug in the DOM Parsing and Serialization spec,
where we're not told how to serialize CDATASection nodes.

Spec bug: https://github.com/w3c/DOM-Parsing/issues/38
Andreas Kling 8 ay önce
ebeveyn
işleme
6ffc7ea36d

+ 1 - 1
Libraries/LibWeb/DOM/Node.h

@@ -110,7 +110,7 @@ public:
 
     NodeType type() const { return m_type; }
     bool is_element() const { return type() == NodeType::ELEMENT_NODE; }
-    bool is_text() const { return type() == NodeType::TEXT_NODE; }
+    bool is_text() const { return type() == NodeType::TEXT_NODE || type() == NodeType::CDATA_SECTION_NODE; }
     bool is_document() const { return type() == NodeType::DOCUMENT_NODE; }
     bool is_document_type() const { return type() == NodeType::DOCUMENT_TYPE_NODE; }
     bool is_comment() const { return type() == NodeType::COMMENT_NODE; }

+ 8 - 6
Libraries/LibWeb/DOMParsing/XMLSerializer.cpp

@@ -196,6 +196,14 @@ WebIDL::ExceptionOr<String> serialize_node_to_xml_string_impl(GC::Ref<DOM::Node
         return serialize_comment(static_cast<DOM::Comment const&>(*root), require_well_formed);
     }
 
+    // NOTE: CDATASection comes before Text since CDATASection is a subclass of Text.
+    if (is<DOM::CDATASection>(*root)) {
+        // Note: Serialization of CDATASection nodes is not mentioned in the specification, but treating CDATASection nodes as
+        // text leads to incorrect serialization.
+        // Spec bug: https://github.com/w3c/DOM-Parsing/issues/38
+        return serialize_cdata_section(static_cast<DOM::CDATASection const&>(*root), require_well_formed);
+    }
+
     if (is<DOM::Text>(*root)) {
         // -> Text
         //    Run the algorithm for XML serializing a Text node node.
@@ -220,12 +228,6 @@ WebIDL::ExceptionOr<String> serialize_node_to_xml_string_impl(GC::Ref<DOM::Node
         return serialize_processing_instruction(static_cast<DOM::ProcessingInstruction const&>(*root), require_well_formed);
     }
 
-    if (is<DOM::CDATASection>(*root)) {
-        // Note: Serialization of CDATASection nodes is not mentioned in the specification, but treating CDATASection nodes as
-        // text leads to incorrect serialization.
-        return serialize_cdata_section(static_cast<DOM::CDATASection const&>(*root), require_well_formed);
-    }
-
     if (is<DOM::Attr>(*root)) {
         // -> An Attr object
         //    Return an empty string.

+ 737 - 0
Tests/LibWeb/Text/expected/wpt-import/dom/nodes/Node-properties.txt

@@ -0,0 +1,737 @@
+Summary
+
+Harness status: OK
+
+Rerun
+
+Found 726 tests
+
+718 Pass
+8 Fail
+Details
+Result	Test Name	MessagePass	testDiv.nodeType	
+Pass	testDiv.ownerDocument	
+Pass	testDiv.parentNode	
+Pass	testDiv.parentElement	
+Pass	testDiv.childNodes.length	
+Pass	testDiv.childNodes[0]	
+Pass	testDiv.childNodes[1]	
+Pass	testDiv.childNodes[2]	
+Pass	testDiv.childNodes[3]	
+Pass	testDiv.childNodes[4]	
+Pass	testDiv.childNodes[5]	
+Pass	testDiv.childNodes[6]	
+Pass	testDiv.previousSibling	
+Pass	testDiv.nextSibling	
+Pass	testDiv.textContent	
+Pass	testDiv.namespaceURI	
+Pass	testDiv.prefix	
+Pass	testDiv.localName	
+Pass	testDiv.tagName	
+Pass	testDiv.id	
+Pass	testDiv.children[0]	
+Pass	testDiv.children[1]	
+Pass	testDiv.children[2]	
+Pass	testDiv.children[3]	
+Pass	testDiv.children[4]	
+Pass	testDiv.children[5]	
+Pass	testDiv.previousElementSibling	
+Pass	testDiv.childElementCount	
+Pass	testDiv.nodeName	
+Pass	testDiv.nodeValue	
+Pass	testDiv.children.length	
+Pass	testDiv.className	
+Pass	testDiv.firstElementChild	
+Pass	testDiv.lastElementChild	
+Pass	testDiv.firstChild	
+Pass	testDiv.lastChild	
+Pass	testDiv.hasChildNodes()	
+Pass	detachedDiv.nodeType	
+Pass	detachedDiv.ownerDocument	
+Pass	detachedDiv.parentNode	
+Pass	detachedDiv.parentElement	
+Pass	detachedDiv.childNodes.length	
+Pass	detachedDiv.childNodes[0]	
+Pass	detachedDiv.childNodes[1]	
+Pass	detachedDiv.previousSibling	
+Pass	detachedDiv.nextSibling	
+Pass	detachedDiv.textContent	
+Pass	detachedDiv.namespaceURI	
+Pass	detachedDiv.prefix	
+Pass	detachedDiv.localName	
+Pass	detachedDiv.tagName	
+Pass	detachedDiv.children[0]	
+Pass	detachedDiv.children[1]	
+Pass	detachedDiv.previousElementSibling	
+Pass	detachedDiv.nextElementSibling	
+Pass	detachedDiv.childElementCount	
+Pass	detachedDiv.nodeName	
+Pass	detachedDiv.nodeValue	
+Pass	detachedDiv.children.length	
+Pass	detachedDiv.id	
+Pass	detachedDiv.className	
+Pass	detachedDiv.firstElementChild	
+Pass	detachedDiv.lastElementChild	
+Pass	detachedDiv.firstChild	
+Pass	detachedDiv.lastChild	
+Pass	detachedDiv.hasChildNodes()	
+Pass	detachedPara1.nodeType	
+Pass	detachedPara1.ownerDocument	
+Pass	detachedPara1.parentNode	
+Pass	detachedPara1.parentElement	
+Pass	detachedPara1.childNodes.length	
+Pass	detachedPara1.previousSibling	
+Pass	detachedPara1.nextSibling	
+Pass	detachedPara1.textContent	
+Pass	detachedPara1.namespaceURI	
+Pass	detachedPara1.prefix	
+Pass	detachedPara1.localName	
+Pass	detachedPara1.tagName	
+Pass	detachedPara1.previousElementSibling	
+Pass	detachedPara1.nextElementSibling	
+Pass	detachedPara1.childElementCount	
+Pass	detachedPara1.nodeName	
+Pass	detachedPara1.nodeValue	
+Pass	detachedPara1.children.length	
+Pass	detachedPara1.id	
+Pass	detachedPara1.className	
+Pass	detachedPara1.lastElementChild	
+Pass	detachedPara1.firstElementChild	
+Pass	detachedPara1.firstChild	
+Pass	detachedPara1.lastChild	
+Pass	detachedPara1.hasChildNodes()	
+Pass	detachedPara2.nodeType	
+Pass	detachedPara2.ownerDocument	
+Pass	detachedPara2.parentNode	
+Pass	detachedPara2.parentElement	
+Pass	detachedPara2.childNodes.length	
+Pass	detachedPara2.previousSibling	
+Pass	detachedPara2.nextSibling	
+Pass	detachedPara2.textContent	
+Pass	detachedPara2.namespaceURI	
+Pass	detachedPara2.prefix	
+Pass	detachedPara2.localName	
+Pass	detachedPara2.tagName	
+Pass	detachedPara2.previousElementSibling	
+Pass	detachedPara2.nextElementSibling	
+Pass	detachedPara2.childElementCount	
+Pass	detachedPara2.nodeName	
+Pass	detachedPara2.nodeValue	
+Pass	detachedPara2.children.length	
+Pass	detachedPara2.id	
+Pass	detachedPara2.className	
+Pass	detachedPara2.lastElementChild	
+Pass	detachedPara2.firstElementChild	
+Pass	detachedPara2.firstChild	
+Pass	detachedPara2.lastChild	
+Pass	detachedPara2.hasChildNodes()	
+Pass	document.nodeType	
+Pass	document.childNodes.length	
+Pass	document.childNodes[0]	
+Pass	document.childNodes[1]	
+Pass	document.URL	
+Pass	document.compatMode	
+Pass	document.characterSet	
+Pass	document.contentType	
+Pass	document.doctype	
+Pass	document.nodeName	
+Pass	document.textContent	
+Pass	document.nodeValue	
+Pass	document.nextSibling	
+Pass	document.previousSibling	
+Pass	document.parentElement	
+Pass	document.parentNode	
+Pass	document.ownerDocument	
+Pass	document.documentURI	
+Pass	document.inputEncoding	
+Pass	document.charset	
+Pass	document.firstChild	
+Pass	document.lastChild	
+Pass	document.hasChildNodes()	
+Pass	foreignDoc.nodeType	
+Pass	foreignDoc.childNodes.length	
+Pass	foreignDoc.childNodes[0]	
+Pass	foreignDoc.childNodes[1]	
+Pass	foreignDoc.childNodes[2]	
+Pass	foreignDoc.URL	
+Pass	foreignDoc.compatMode	
+Pass	foreignDoc.characterSet	
+Pass	foreignDoc.contentType	
+Pass	foreignDoc.nodeName	
+Pass	foreignDoc.textContent	
+Pass	foreignDoc.nodeValue	
+Pass	foreignDoc.nextSibling	
+Pass	foreignDoc.previousSibling	
+Pass	foreignDoc.parentElement	
+Pass	foreignDoc.parentNode	
+Pass	foreignDoc.ownerDocument	
+Pass	foreignDoc.documentURI	
+Pass	foreignDoc.inputEncoding	
+Pass	foreignDoc.charset	
+Pass	foreignDoc.firstChild	
+Pass	foreignDoc.lastChild	
+Pass	foreignDoc.hasChildNodes()	
+Pass	foreignPara1.nodeType	
+Pass	foreignPara1.ownerDocument	
+Pass	foreignPara1.parentNode	
+Pass	foreignPara1.parentElement	
+Pass	foreignPara1.childNodes.length	
+Pass	foreignPara1.previousSibling	
+Pass	foreignPara1.nextSibling	
+Pass	foreignPara1.textContent	
+Pass	foreignPara1.namespaceURI	
+Pass	foreignPara1.prefix	
+Pass	foreignPara1.localName	
+Pass	foreignPara1.tagName	
+Pass	foreignPara1.previousElementSibling	
+Pass	foreignPara1.nextElementSibling	
+Pass	foreignPara1.childElementCount	
+Pass	foreignPara1.nodeName	
+Pass	foreignPara1.nodeValue	
+Pass	foreignPara1.children.length	
+Pass	foreignPara1.id	
+Pass	foreignPara1.className	
+Pass	foreignPara1.lastElementChild	
+Pass	foreignPara1.firstElementChild	
+Pass	foreignPara1.firstChild	
+Pass	foreignPara1.lastChild	
+Pass	foreignPara1.hasChildNodes()	
+Pass	foreignPara2.nodeType	
+Pass	foreignPara2.ownerDocument	
+Pass	foreignPara2.parentNode	
+Pass	foreignPara2.parentElement	
+Pass	foreignPara2.childNodes.length	
+Pass	foreignPara2.previousSibling	
+Pass	foreignPara2.nextSibling	
+Pass	foreignPara2.textContent	
+Pass	foreignPara2.namespaceURI	
+Pass	foreignPara2.prefix	
+Pass	foreignPara2.localName	
+Pass	foreignPara2.tagName	
+Pass	foreignPara2.previousElementSibling	
+Pass	foreignPara2.nextElementSibling	
+Pass	foreignPara2.childElementCount	
+Pass	foreignPara2.nodeName	
+Pass	foreignPara2.nodeValue	
+Pass	foreignPara2.children.length	
+Pass	foreignPara2.id	
+Pass	foreignPara2.className	
+Pass	foreignPara2.lastElementChild	
+Pass	foreignPara2.firstElementChild	
+Pass	foreignPara2.firstChild	
+Pass	foreignPara2.lastChild	
+Pass	foreignPara2.hasChildNodes()	
+Pass	xmlDoc.nodeType	
+Fail	xmlDoc.childNodes.length	
+Pass	xmlDoc.childNodes[0]	
+Pass	xmlDoc.childNodes[1]	
+Fail	xmlDoc.childNodes[2]	
+Fail	xmlDoc.childNodes[3]	
+Pass	xmlDoc.URL	
+Pass	xmlDoc.compatMode	
+Pass	xmlDoc.characterSet	
+Pass	xmlDoc.contentType	
+Pass	xmlDoc.nodeName	
+Pass	xmlDoc.textContent	
+Pass	xmlDoc.nodeValue	
+Pass	xmlDoc.nextSibling	
+Pass	xmlDoc.previousSibling	
+Pass	xmlDoc.parentElement	
+Pass	xmlDoc.parentNode	
+Pass	xmlDoc.ownerDocument	
+Pass	xmlDoc.documentURI	
+Pass	xmlDoc.inputEncoding	
+Pass	xmlDoc.charset	
+Pass	xmlDoc.firstChild	
+Pass	xmlDoc.lastChild	
+Pass	xmlDoc.hasChildNodes()	
+Pass	xmlElement.nodeType	
+Pass	xmlElement.ownerDocument	
+Pass	xmlElement.parentNode	
+Pass	xmlElement.parentElement	
+Pass	xmlElement.childNodes.length	
+Pass	xmlElement.childNodes[0]	
+Pass	xmlElement.previousSibling	
+Fail	xmlElement.nextSibling	
+Pass	xmlElement.textContent	
+Pass	xmlElement.namespaceURI	
+Pass	xmlElement.prefix	
+Pass	xmlElement.localName	
+Pass	xmlElement.tagName	
+Pass	xmlElement.previousElementSibling	
+Pass	xmlElement.nextElementSibling	
+Pass	xmlElement.childElementCount	
+Pass	xmlElement.nodeName	
+Pass	xmlElement.nodeValue	
+Pass	xmlElement.children.length	
+Pass	xmlElement.id	
+Pass	xmlElement.className	
+Pass	xmlElement.lastElementChild	
+Pass	xmlElement.firstElementChild	
+Pass	xmlElement.firstChild	
+Pass	xmlElement.lastChild	
+Pass	xmlElement.hasChildNodes()	
+Pass	detachedXmlElement.nodeType	
+Pass	detachedXmlElement.ownerDocument	
+Pass	detachedXmlElement.parentNode	
+Pass	detachedXmlElement.parentElement	
+Pass	detachedXmlElement.childNodes.length	
+Pass	detachedXmlElement.previousSibling	
+Pass	detachedXmlElement.nextSibling	
+Pass	detachedXmlElement.textContent	
+Pass	detachedXmlElement.namespaceURI	
+Pass	detachedXmlElement.prefix	
+Pass	detachedXmlElement.localName	
+Pass	detachedXmlElement.tagName	
+Pass	detachedXmlElement.previousElementSibling	
+Pass	detachedXmlElement.nextElementSibling	
+Pass	detachedXmlElement.childElementCount	
+Pass	detachedXmlElement.nodeName	
+Pass	detachedXmlElement.nodeValue	
+Pass	detachedXmlElement.children.length	
+Pass	detachedXmlElement.id	
+Pass	detachedXmlElement.className	
+Pass	detachedXmlElement.lastElementChild	
+Pass	detachedXmlElement.firstElementChild	
+Pass	detachedXmlElement.lastChild	
+Pass	detachedXmlElement.firstChild	
+Pass	detachedXmlElement.hasChildNodes()	
+Pass	detachedTextNode.nodeType	
+Pass	detachedTextNode.ownerDocument	
+Pass	detachedTextNode.parentNode	
+Pass	detachedTextNode.parentElement	
+Pass	detachedTextNode.previousSibling	
+Pass	detachedTextNode.nextSibling	
+Pass	detachedTextNode.nodeValue	
+Pass	detachedTextNode.wholeText	
+Pass	detachedTextNode.nodeName	
+Pass	detachedTextNode.childNodes.length	
+Pass	detachedTextNode.data	
+Pass	detachedTextNode.textContent	
+Pass	detachedTextNode.length	
+Pass	detachedTextNode.lastChild	
+Pass	detachedTextNode.firstChild	
+Pass	detachedTextNode.hasChildNodes()	
+Pass	foreignTextNode.nodeType	
+Pass	foreignTextNode.ownerDocument	
+Pass	foreignTextNode.parentNode	
+Pass	foreignTextNode.parentElement	
+Pass	foreignTextNode.previousSibling	
+Pass	foreignTextNode.nextSibling	
+Pass	foreignTextNode.nodeValue	
+Pass	foreignTextNode.wholeText	
+Pass	foreignTextNode.nodeName	
+Pass	foreignTextNode.childNodes.length	
+Pass	foreignTextNode.data	
+Pass	foreignTextNode.textContent	
+Pass	foreignTextNode.length	
+Pass	foreignTextNode.lastChild	
+Pass	foreignTextNode.firstChild	
+Pass	foreignTextNode.hasChildNodes()	
+Pass	detachedForeignTextNode.nodeType	
+Pass	detachedForeignTextNode.ownerDocument	
+Pass	detachedForeignTextNode.parentNode	
+Pass	detachedForeignTextNode.parentElement	
+Pass	detachedForeignTextNode.previousSibling	
+Pass	detachedForeignTextNode.nextSibling	
+Pass	detachedForeignTextNode.nodeValue	
+Pass	detachedForeignTextNode.wholeText	
+Pass	detachedForeignTextNode.nodeName	
+Pass	detachedForeignTextNode.childNodes.length	
+Pass	detachedForeignTextNode.data	
+Pass	detachedForeignTextNode.textContent	
+Pass	detachedForeignTextNode.length	
+Pass	detachedForeignTextNode.lastChild	
+Pass	detachedForeignTextNode.firstChild	
+Pass	detachedForeignTextNode.hasChildNodes()	
+Pass	xmlTextNode.nodeType	
+Pass	xmlTextNode.ownerDocument	
+Pass	xmlTextNode.parentNode	
+Pass	xmlTextNode.parentElement	
+Pass	xmlTextNode.previousSibling	
+Pass	xmlTextNode.nextSibling	
+Pass	xmlTextNode.nodeValue	
+Pass	xmlTextNode.wholeText	
+Pass	xmlTextNode.nodeName	
+Pass	xmlTextNode.childNodes.length	
+Pass	xmlTextNode.data	
+Pass	xmlTextNode.textContent	
+Pass	xmlTextNode.length	
+Pass	xmlTextNode.lastChild	
+Pass	xmlTextNode.firstChild	
+Pass	xmlTextNode.hasChildNodes()	
+Pass	detachedXmlTextNode.nodeType	
+Pass	detachedXmlTextNode.ownerDocument	
+Pass	detachedXmlTextNode.parentNode	
+Pass	detachedXmlTextNode.parentElement	
+Pass	detachedXmlTextNode.previousSibling	
+Pass	detachedXmlTextNode.nextSibling	
+Pass	detachedXmlTextNode.nodeValue	
+Pass	detachedXmlTextNode.wholeText	
+Pass	detachedXmlTextNode.nodeName	
+Pass	detachedXmlTextNode.childNodes.length	
+Pass	detachedXmlTextNode.data	
+Pass	detachedXmlTextNode.textContent	
+Pass	detachedXmlTextNode.length	
+Pass	detachedXmlTextNode.lastChild	
+Pass	detachedXmlTextNode.firstChild	
+Pass	detachedXmlTextNode.hasChildNodes()	
+Pass	processingInstruction.nodeType	
+Pass	processingInstruction.ownerDocument	
+Fail	processingInstruction.parentNode	
+Pass	processingInstruction.parentElement	
+Fail	processingInstruction.previousSibling	
+Fail	processingInstruction.nextSibling	
+Pass	processingInstruction.nodeValue	
+Pass	processingInstruction.target	
+Pass	processingInstruction.nodeName	
+Pass	processingInstruction.childNodes.length	
+Pass	processingInstruction.data	
+Pass	processingInstruction.textContent	
+Pass	processingInstruction.length	
+Pass	processingInstruction.lastChild	
+Pass	processingInstruction.firstChild	
+Pass	processingInstruction.hasChildNodes()	
+Pass	detachedProcessingInstruction.nodeType	
+Pass	detachedProcessingInstruction.ownerDocument	
+Pass	detachedProcessingInstruction.parentNode	
+Pass	detachedProcessingInstruction.parentElement	
+Pass	detachedProcessingInstruction.previousSibling	
+Pass	detachedProcessingInstruction.nextSibling	
+Pass	detachedProcessingInstruction.nodeValue	
+Pass	detachedProcessingInstruction.target	
+Pass	detachedProcessingInstruction.nodeName	
+Pass	detachedProcessingInstruction.childNodes.length	
+Pass	detachedProcessingInstruction.data	
+Pass	detachedProcessingInstruction.textContent	
+Pass	detachedProcessingInstruction.length	
+Pass	detachedProcessingInstruction.lastChild	
+Pass	detachedProcessingInstruction.firstChild	
+Pass	detachedProcessingInstruction.hasChildNodes()	
+Pass	comment.nodeType	
+Pass	comment.ownerDocument	
+Pass	comment.parentNode	
+Pass	comment.parentElement	
+Pass	comment.previousSibling	
+Pass	comment.nextSibling	
+Pass	comment.nodeValue	
+Pass	comment.nodeName	
+Pass	comment.childNodes.length	
+Pass	comment.data	
+Pass	comment.textContent	
+Pass	comment.length	
+Pass	comment.lastChild	
+Pass	comment.firstChild	
+Pass	comment.hasChildNodes()	
+Pass	detachedComment.nodeType	
+Pass	detachedComment.ownerDocument	
+Pass	detachedComment.parentNode	
+Pass	detachedComment.parentElement	
+Pass	detachedComment.previousSibling	
+Pass	detachedComment.nextSibling	
+Pass	detachedComment.nodeValue	
+Pass	detachedComment.nodeName	
+Pass	detachedComment.childNodes.length	
+Pass	detachedComment.data	
+Pass	detachedComment.textContent	
+Pass	detachedComment.length	
+Pass	detachedComment.lastChild	
+Pass	detachedComment.firstChild	
+Pass	detachedComment.hasChildNodes()	
+Pass	foreignComment.nodeType	
+Pass	foreignComment.ownerDocument	
+Pass	foreignComment.parentNode	
+Pass	foreignComment.parentElement	
+Pass	foreignComment.previousSibling	
+Pass	foreignComment.nextSibling	
+Pass	foreignComment.nodeValue	
+Pass	foreignComment.nodeName	
+Pass	foreignComment.childNodes.length	
+Pass	foreignComment.data	
+Pass	foreignComment.textContent	
+Pass	foreignComment.length	
+Pass	foreignComment.lastChild	
+Pass	foreignComment.firstChild	
+Pass	foreignComment.hasChildNodes()	
+Pass	detachedForeignComment.nodeType	
+Pass	detachedForeignComment.ownerDocument	
+Pass	detachedForeignComment.parentNode	
+Pass	detachedForeignComment.parentElement	
+Pass	detachedForeignComment.previousSibling	
+Pass	detachedForeignComment.nextSibling	
+Pass	detachedForeignComment.nodeValue	
+Pass	detachedForeignComment.nodeName	
+Pass	detachedForeignComment.childNodes.length	
+Pass	detachedForeignComment.data	
+Pass	detachedForeignComment.textContent	
+Pass	detachedForeignComment.length	
+Pass	detachedForeignComment.lastChild	
+Pass	detachedForeignComment.firstChild	
+Pass	detachedForeignComment.hasChildNodes()	
+Pass	xmlComment.nodeType	
+Pass	xmlComment.ownerDocument	
+Pass	xmlComment.parentNode	
+Pass	xmlComment.parentElement	
+Fail	xmlComment.previousSibling	
+Pass	xmlComment.nextSibling	
+Pass	xmlComment.nodeValue	
+Pass	xmlComment.nodeName	
+Pass	xmlComment.childNodes.length	
+Pass	xmlComment.data	
+Pass	xmlComment.textContent	
+Pass	xmlComment.length	
+Pass	xmlComment.lastChild	
+Pass	xmlComment.firstChild	
+Pass	xmlComment.hasChildNodes()	
+Pass	detachedXmlComment.nodeType	
+Pass	detachedXmlComment.ownerDocument	
+Pass	detachedXmlComment.parentNode	
+Pass	detachedXmlComment.parentElement	
+Pass	detachedXmlComment.previousSibling	
+Pass	detachedXmlComment.nextSibling	
+Pass	detachedXmlComment.nodeValue	
+Pass	detachedXmlComment.nodeName	
+Pass	detachedXmlComment.childNodes.length	
+Pass	detachedXmlComment.data	
+Pass	detachedXmlComment.textContent	
+Pass	detachedXmlComment.length	
+Pass	detachedXmlComment.lastChild	
+Pass	detachedXmlComment.firstChild	
+Pass	detachedXmlComment.hasChildNodes()	
+Pass	docfrag.nodeType	
+Pass	docfrag.ownerDocument	
+Pass	docfrag.childNodes.length	
+Pass	docfrag.textContent	
+Pass	docfrag.nodeName	
+Pass	docfrag.nodeValue	
+Pass	docfrag.nextSibling	
+Pass	docfrag.previousSibling	
+Pass	docfrag.parentElement	
+Pass	docfrag.parentNode	
+Pass	docfrag.lastChild	
+Pass	docfrag.firstChild	
+Pass	docfrag.hasChildNodes()	
+Pass	foreignDocfrag.nodeType	
+Pass	foreignDocfrag.ownerDocument	
+Pass	foreignDocfrag.childNodes.length	
+Pass	foreignDocfrag.textContent	
+Pass	foreignDocfrag.nodeName	
+Pass	foreignDocfrag.nodeValue	
+Pass	foreignDocfrag.nextSibling	
+Pass	foreignDocfrag.previousSibling	
+Pass	foreignDocfrag.parentElement	
+Pass	foreignDocfrag.parentNode	
+Pass	foreignDocfrag.lastChild	
+Pass	foreignDocfrag.firstChild	
+Pass	foreignDocfrag.hasChildNodes()	
+Pass	xmlDocfrag.nodeType	
+Pass	xmlDocfrag.ownerDocument	
+Pass	xmlDocfrag.childNodes.length	
+Pass	xmlDocfrag.textContent	
+Pass	xmlDocfrag.nodeName	
+Pass	xmlDocfrag.nodeValue	
+Pass	xmlDocfrag.nextSibling	
+Pass	xmlDocfrag.previousSibling	
+Pass	xmlDocfrag.parentElement	
+Pass	xmlDocfrag.parentNode	
+Pass	xmlDocfrag.lastChild	
+Pass	xmlDocfrag.firstChild	
+Pass	xmlDocfrag.hasChildNodes()	
+Pass	doctype.nodeType	
+Pass	doctype.ownerDocument	
+Pass	doctype.parentNode	
+Pass	doctype.previousSibling	
+Pass	doctype.nextSibling	
+Pass	doctype.name	
+Pass	doctype.publicId	
+Pass	doctype.systemId	
+Pass	doctype.nodeName	
+Pass	doctype.childNodes.length	
+Pass	doctype.textContent	
+Pass	doctype.nodeValue	
+Pass	doctype.parentElement	
+Pass	doctype.lastChild	
+Pass	doctype.firstChild	
+Pass	doctype.hasChildNodes()	
+Pass	foreignDoctype.nodeType	
+Pass	foreignDoctype.ownerDocument	
+Pass	foreignDoctype.parentNode	
+Pass	foreignDoctype.previousSibling	
+Pass	foreignDoctype.nextSibling	
+Pass	foreignDoctype.name	
+Pass	foreignDoctype.publicId	
+Pass	foreignDoctype.systemId	
+Pass	foreignDoctype.nodeName	
+Pass	foreignDoctype.childNodes.length	
+Pass	foreignDoctype.textContent	
+Pass	foreignDoctype.nodeValue	
+Pass	foreignDoctype.parentElement	
+Pass	foreignDoctype.lastChild	
+Pass	foreignDoctype.firstChild	
+Pass	foreignDoctype.hasChildNodes()	
+Pass	xmlDoctype.nodeType	
+Pass	xmlDoctype.ownerDocument	
+Pass	xmlDoctype.parentNode	
+Pass	xmlDoctype.previousSibling	
+Pass	xmlDoctype.nextSibling	
+Pass	xmlDoctype.name	
+Pass	xmlDoctype.publicId	
+Pass	xmlDoctype.systemId	
+Pass	xmlDoctype.nodeName	
+Pass	xmlDoctype.childNodes.length	
+Pass	xmlDoctype.textContent	
+Pass	xmlDoctype.nodeValue	
+Pass	xmlDoctype.parentElement	
+Pass	xmlDoctype.lastChild	
+Pass	xmlDoctype.firstChild	
+Pass	xmlDoctype.hasChildNodes()	
+Pass	paras[0].nodeType	
+Pass	paras[0].ownerDocument	
+Pass	paras[0].parentNode	
+Pass	paras[0].parentElement	
+Pass	paras[0].childNodes.length	
+Pass	paras[0].previousSibling	
+Pass	paras[0].nextSibling	
+Pass	paras[0].textContent	
+Pass	paras[0].namespaceURI	
+Pass	paras[0].prefix	
+Pass	paras[0].localName	
+Pass	paras[0].tagName	
+Pass	paras[0].id	
+Pass	paras[0].previousElementSibling	
+Pass	paras[0].nextElementSibling	
+Pass	paras[0].childElementCount	
+Pass	paras[0].nodeName	
+Pass	paras[0].nodeValue	
+Pass	paras[0].children.length	
+Pass	paras[0].className	
+Pass	paras[0].lastElementChild	
+Pass	paras[0].firstElementChild	
+Pass	paras[0].firstChild	
+Pass	paras[0].lastChild	
+Pass	paras[0].hasChildNodes()	
+Pass	paras[1].nodeType	
+Pass	paras[1].ownerDocument	
+Pass	paras[1].parentNode	
+Pass	paras[1].parentElement	
+Pass	paras[1].childNodes.length	
+Pass	paras[1].previousSibling	
+Pass	paras[1].nextSibling	
+Pass	paras[1].textContent	
+Pass	paras[1].namespaceURI	
+Pass	paras[1].prefix	
+Pass	paras[1].localName	
+Pass	paras[1].tagName	
+Pass	paras[1].id	
+Pass	paras[1].previousElementSibling	
+Pass	paras[1].nextElementSibling	
+Pass	paras[1].childElementCount	
+Pass	paras[1].nodeName	
+Pass	paras[1].nodeValue	
+Pass	paras[1].children.length	
+Pass	paras[1].className	
+Pass	paras[1].lastElementChild	
+Pass	paras[1].firstElementChild	
+Pass	paras[1].firstChild	
+Pass	paras[1].lastChild	
+Pass	paras[1].hasChildNodes()	
+Pass	paras[2].nodeType	
+Pass	paras[2].ownerDocument	
+Pass	paras[2].parentNode	
+Pass	paras[2].parentElement	
+Pass	paras[2].childNodes.length	
+Pass	paras[2].previousSibling	
+Pass	paras[2].nextSibling	
+Pass	paras[2].textContent	
+Pass	paras[2].namespaceURI	
+Pass	paras[2].prefix	
+Pass	paras[2].localName	
+Pass	paras[2].tagName	
+Pass	paras[2].id	
+Pass	paras[2].previousElementSibling	
+Pass	paras[2].nextElementSibling	
+Pass	paras[2].childElementCount	
+Pass	paras[2].nodeName	
+Pass	paras[2].nodeValue	
+Pass	paras[2].children.length	
+Pass	paras[2].className	
+Pass	paras[2].lastElementChild	
+Pass	paras[2].firstElementChild	
+Pass	paras[2].firstChild	
+Pass	paras[2].lastChild	
+Pass	paras[2].hasChildNodes()	
+Pass	paras[3].nodeType	
+Pass	paras[3].ownerDocument	
+Pass	paras[3].parentNode	
+Pass	paras[3].parentElement	
+Pass	paras[3].childNodes.length	
+Pass	paras[3].previousSibling	
+Pass	paras[3].nextSibling	
+Pass	paras[3].textContent	
+Pass	paras[3].namespaceURI	
+Pass	paras[3].prefix	
+Pass	paras[3].localName	
+Pass	paras[3].tagName	
+Pass	paras[3].id	
+Pass	paras[3].previousElementSibling	
+Pass	paras[3].nextElementSibling	
+Pass	paras[3].childElementCount	
+Pass	paras[3].nodeName	
+Pass	paras[3].nodeValue	
+Pass	paras[3].children.length	
+Pass	paras[3].className	
+Pass	paras[3].lastElementChild	
+Pass	paras[3].firstElementChild	
+Pass	paras[3].firstChild	
+Pass	paras[3].lastChild	
+Pass	paras[3].hasChildNodes()	
+Pass	paras[4].nodeType	
+Pass	paras[4].ownerDocument	
+Pass	paras[4].parentNode	
+Pass	paras[4].parentElement	
+Pass	paras[4].childNodes.length	
+Pass	paras[4].previousSibling	
+Pass	paras[4].nextSibling	
+Pass	paras[4].textContent	
+Pass	paras[4].namespaceURI	
+Pass	paras[4].prefix	
+Pass	paras[4].localName	
+Pass	paras[4].tagName	
+Pass	paras[4].id	
+Pass	paras[4].previousElementSibling	
+Pass	paras[4].nextElementSibling	
+Pass	paras[4].childElementCount	
+Pass	paras[4].nodeName	
+Pass	paras[4].nodeValue	
+Pass	paras[4].children.length	
+Pass	paras[4].className	
+Pass	paras[4].lastElementChild	
+Pass	paras[4].firstElementChild	
+Pass	paras[4].firstChild	
+Pass	paras[4].lastChild	
+Pass	paras[4].hasChildNodes()	
+Pass	paras[5].nodeType	
+Pass	paras[5].ownerDocument	
+Pass	paras[5].parentNode	
+Pass	paras[5].parentElement	
+Pass	paras[5].childNodes.length	
+Pass	paras[5].previousSibling	
+Pass	paras[5].nextSibling	
+Pass	paras[5].textContent	
+Pass	paras[5].namespaceURI	
+Pass	paras[5].prefix	
+Pass	paras[5].localName	
+Pass	paras[5].tagName	
+Pass	paras[5].previousElementSibling	
+Pass	paras[5].nextElementSibling	
+Pass	paras[5].childElementCount	
+Pass	paras[5].nodeName	
+Pass	paras[5].nodeValue	
+Pass	paras[5].children.length	
+Pass	paras[5].id	
+Pass	paras[5].className	
+Pass	paras[5].lastElementChild	
+Pass	paras[5].firstElementChild	
+Pass	paras[5].firstChild	
+Pass	paras[5].lastChild	
+Pass	paras[5].hasChildNodes()	

+ 1089 - 0
Tests/LibWeb/Text/input/wpt-import/dom/common.js

@@ -0,0 +1,1089 @@
+"use strict";
+// Written by Aryeh Gregor <ayg@aryeh.name>
+
+// TODO: iframes, contenteditable/designMode
+
+// Everything is done in functions in this test harness, so we have to declare
+// all the variables before use to make sure they can be reused.
+var testDiv, paras, detachedDiv, detachedPara1, detachedPara2,
+    foreignDoc, foreignPara1, foreignPara2, xmlDoc, xmlElement,
+    detachedXmlElement, detachedTextNode, foreignTextNode,
+    detachedForeignTextNode, xmlTextNode, detachedXmlTextNode,
+    processingInstruction, detachedProcessingInstruction, comment,
+    detachedComment, foreignComment, detachedForeignComment, xmlComment,
+    detachedXmlComment, docfrag, foreignDocfrag, xmlDocfrag, doctype,
+    foreignDoctype, xmlDoctype;
+var testRangesShort, testRanges, testPoints, testNodesShort, testNodes;
+
+function setupRangeTests() {
+    testDiv = document.querySelector("#test");
+    if (testDiv) {
+        testDiv.parentNode.removeChild(testDiv);
+    }
+    testDiv = document.createElement("div");
+    testDiv.id = "test";
+    document.body.insertBefore(testDiv, document.body.firstChild);
+
+    paras = [];
+    paras.push(document.createElement("p"));
+    paras[0].setAttribute("id", "a");
+    // Test some diacritics, to make sure browsers are using code units here
+    // and not something like grapheme clusters.
+    paras[0].textContent = "A\u0308b\u0308c\u0308d\u0308e\u0308f\u0308g\u0308h\u0308\n";
+    testDiv.appendChild(paras[0]);
+
+    paras.push(document.createElement("p"));
+    paras[1].setAttribute("id", "b");
+    paras[1].setAttribute("style", "display:none");
+    paras[1].textContent = "Ijklmnop\n";
+    testDiv.appendChild(paras[1]);
+
+    paras.push(document.createElement("p"));
+    paras[2].setAttribute("id", "c");
+    paras[2].textContent = "Qrstuvwx";
+    testDiv.appendChild(paras[2]);
+
+    paras.push(document.createElement("p"));
+    paras[3].setAttribute("id", "d");
+    paras[3].setAttribute("style", "display:none");
+    paras[3].textContent = "Yzabcdef";
+    testDiv.appendChild(paras[3]);
+
+    paras.push(document.createElement("p"));
+    paras[4].setAttribute("id", "e");
+    paras[4].setAttribute("style", "display:none");
+    paras[4].textContent = "Ghijklmn";
+    testDiv.appendChild(paras[4]);
+
+    paras.push(document.createElement("p"));
+    const xmlDocument = new Document();
+    paras[5].appendChild(xmlDocument.createCDATASection("1234"));
+    paras[5].appendChild(xmlDocument.createCDATASection("5678"));
+    paras[5].append("9012");
+    testDiv.appendChild(paras[5]);
+
+    detachedDiv = document.createElement("div");
+    detachedPara1 = document.createElement("p");
+    detachedPara1.appendChild(document.createTextNode("Opqrstuv"));
+    detachedPara2 = document.createElement("p");
+    detachedPara2.appendChild(document.createTextNode("Wxyzabcd"));
+    detachedDiv.appendChild(detachedPara1);
+    detachedDiv.appendChild(detachedPara2);
+
+    // Opera doesn't automatically create a doctype for a new HTML document,
+    // contrary to spec.  It also doesn't let you add doctypes to documents
+    // after the fact through any means I've tried.  So foreignDoc in Opera
+    // will have no doctype, foreignDoctype will be null, and Opera will fail
+    // some tests somewhat mysteriously as a result.
+    foreignDoc = document.implementation.createHTMLDocument("");
+    foreignPara1 = foreignDoc.createElement("p");
+    foreignPara1.appendChild(foreignDoc.createTextNode("Efghijkl"));
+    foreignPara2 = foreignDoc.createElement("p");
+    foreignPara2.appendChild(foreignDoc.createTextNode("Mnopqrst"));
+    foreignDoc.body.appendChild(foreignPara1);
+    foreignDoc.body.appendChild(foreignPara2);
+
+    // Now we get to do really silly stuff, which nobody in the universe is
+    // ever going to actually do, but the spec defines behavior, so too bad.
+    // Testing is fun!
+    xmlDoctype = document.implementation.createDocumentType("qorflesnorf", "abcde", "x\"'y");
+    xmlDoc = document.implementation.createDocument(null, null, xmlDoctype);
+    detachedXmlElement = xmlDoc.createElement("everyone-hates-hyphenated-element-names");
+    detachedTextNode = document.createTextNode("Uvwxyzab");
+    detachedForeignTextNode = foreignDoc.createTextNode("Cdefghij");
+    detachedXmlTextNode = xmlDoc.createTextNode("Klmnopqr");
+    // PIs only exist in XML documents, so don't bother with document or
+    // foreignDoc.
+    detachedProcessingInstruction = xmlDoc.createProcessingInstruction("whippoorwill", "chirp chirp chirp");
+    detachedComment = document.createComment("Stuvwxyz");
+    // Hurrah, we finally got to "z" at the end!
+    detachedForeignComment = foreignDoc.createComment("אריה יהודה");
+    detachedXmlComment = xmlDoc.createComment("בן חיים אליעזר");
+
+    // We should also test with document fragments that actually contain stuff
+    // . . . but, maybe later.
+    docfrag = document.createDocumentFragment();
+    foreignDocfrag = foreignDoc.createDocumentFragment();
+    xmlDocfrag = xmlDoc.createDocumentFragment();
+
+    xmlElement = xmlDoc.createElement("igiveuponcreativenames");
+    xmlTextNode = xmlDoc.createTextNode("do re mi fa so la ti");
+    xmlElement.appendChild(xmlTextNode);
+    processingInstruction = xmlDoc.createProcessingInstruction("somePI", 'Did you know that ":syn sync fromstart" is very useful when using vim to edit large amounts of JavaScript embedded in HTML?');
+    xmlDoc.appendChild(xmlElement);
+    xmlDoc.appendChild(processingInstruction);
+    xmlComment = xmlDoc.createComment("I maliciously created a comment that will break incautious XML serializers, but Firefox threw an exception, so all I got was this lousy T-shirt");
+    xmlDoc.appendChild(xmlComment);
+
+    comment = document.createComment("Alphabet soup?");
+    testDiv.appendChild(comment);
+
+    foreignComment = foreignDoc.createComment('"Commenter" and "commentator" mean different things.  I\'ve seen non-native speakers trip up on this.');
+    foreignDoc.appendChild(foreignComment);
+    foreignTextNode = foreignDoc.createTextNode("I admit that I harbor doubts about whether we really need so many things to test, but it's too late to stop now.");
+    foreignDoc.body.appendChild(foreignTextNode);
+
+    doctype = document.doctype;
+    foreignDoctype = foreignDoc.doctype;
+
+    testRangesShort = [
+        // Various ranges within the text node children of different
+        // paragraphs.  All should be valid.
+        "[paras[0].firstChild, 0, paras[0].firstChild, 0]",
+        "[paras[0].firstChild, 0, paras[0].firstChild, 1]",
+        "[paras[0].firstChild, 2, paras[0].firstChild, 8]",
+        "[paras[0].firstChild, 2, paras[0].firstChild, 9]",
+        "[paras[1].firstChild, 0, paras[1].firstChild, 0]",
+        "[paras[1].firstChild, 2, paras[1].firstChild, 9]",
+        "[paras[5].firstChild, 2, paras[5].lastChild, 4]",
+        "[paras[5].firstChild, 1, paras[5].firstChild, 3]",
+        "[detachedPara1.firstChild, 0, detachedPara1.firstChild, 0]",
+        "[detachedPara1.firstChild, 2, detachedPara1.firstChild, 8]",
+        "[foreignPara1.firstChild, 0, foreignPara1.firstChild, 0]",
+        "[foreignPara1.firstChild, 2, foreignPara1.firstChild, 8]",
+        // Now try testing some elements, not just text nodes.
+        "[document.documentElement, 0, document.documentElement, 1]",
+        "[document.documentElement, 0, document.documentElement, 2]",
+        "[document.documentElement, 1, document.documentElement, 2]",
+        "[document.head, 1, document.head, 1]",
+        "[document.body, 4, document.body, 5]",
+        "[foreignDoc.documentElement, 0, foreignDoc.documentElement, 1]",
+        "[paras[0], 0, paras[0], 1]",
+        "[detachedPara1, 0, detachedPara1, 1]",
+        // Now try some ranges that span elements.
+        "[paras[0].firstChild, 0, paras[1].firstChild, 0]",
+        "[paras[0].firstChild, 0, paras[1].firstChild, 8]",
+        "[paras[0].firstChild, 3, paras[3], 1]",
+        // How about something that spans a node and its descendant?
+        "[paras[0], 0, paras[0].firstChild, 7]",
+        "[testDiv, 2, paras[4], 1]",
+        // Then a few more interesting things just for good measure.
+        "[document, 0, document, 1]",
+        "[document, 0, document, 2]",
+        "[comment, 2, comment, 3]",
+        "[testDiv, 0, comment, 5]",
+        "[foreignDoc, 1, foreignComment, 2]",
+        "[foreignDoc.body, 0, foreignTextNode, 36]",
+        "[xmlDoc, 1, xmlComment, 0]",
+        "[detachedTextNode, 0, detachedTextNode, 8]",
+        "[detachedForeignTextNode, 0, detachedForeignTextNode, 8]",
+        "[detachedXmlTextNode, 0, detachedXmlTextNode, 8]",
+        "[detachedComment, 3, detachedComment, 4]",
+        "[detachedForeignComment, 0, detachedForeignComment, 1]",
+        "[detachedXmlComment, 2, detachedXmlComment, 6]",
+        "[docfrag, 0, docfrag, 0]",
+        "[processingInstruction, 0, processingInstruction, 4]",
+    ];
+
+    testRanges = testRangesShort.concat([
+        "[paras[1].firstChild, 0, paras[1].firstChild, 1]",
+        "[paras[1].firstChild, 2, paras[1].firstChild, 8]",
+        "[detachedPara1.firstChild, 0, detachedPara1.firstChild, 1]",
+        "[foreignPara1.firstChild, 0, foreignPara1.firstChild, 1]",
+        "[foreignDoc.head, 1, foreignDoc.head, 1]",
+        "[foreignDoc.body, 0, foreignDoc.body, 0]",
+        "[paras[0], 0, paras[0], 0]",
+        "[detachedPara1, 0, detachedPara1, 0]",
+        "[testDiv, 1, paras[2].firstChild, 5]",
+        "[document.documentElement, 1, document.body, 0]",
+        "[foreignDoc.documentElement, 1, foreignDoc.body, 0]",
+        "[document, 1, document, 2]",
+        "[paras[2].firstChild, 4, comment, 2]",
+        "[paras[3], 1, comment, 8]",
+        "[foreignDoc, 0, foreignDoc, 0]",
+        "[xmlDoc, 0, xmlDoc, 0]",
+        "[detachedForeignTextNode, 7, detachedForeignTextNode, 7]",
+        "[detachedXmlTextNode, 7, detachedXmlTextNode, 7]",
+        "[detachedComment, 5, detachedComment, 5]",
+        "[detachedForeignComment, 4, detachedForeignComment, 4]",
+        "[foreignDocfrag, 0, foreignDocfrag, 0]",
+        "[xmlDocfrag, 0, xmlDocfrag, 0]",
+    ]);
+
+    testPoints = [
+        // Various positions within the page, some invalid.  Remember that
+        // paras[0] is visible, and paras[1] is display: none.
+        "[paras[0].firstChild, -1]",
+        "[paras[0].firstChild, 0]",
+        "[paras[0].firstChild, 1]",
+        "[paras[0].firstChild, 2]",
+        "[paras[0].firstChild, 8]",
+        "[paras[0].firstChild, 9]",
+        "[paras[0].firstChild, 10]",
+        "[paras[0].firstChild, 65535]",
+        "[paras[1].firstChild, -1]",
+        "[paras[1].firstChild, 0]",
+        "[paras[1].firstChild, 1]",
+        "[paras[1].firstChild, 2]",
+        "[paras[1].firstChild, 8]",
+        "[paras[1].firstChild, 9]",
+        "[paras[1].firstChild, 10]",
+        "[paras[1].firstChild, 65535]",
+        "[paras[5].firstChild, 2]",
+        "[paras[5].firstChild, 20]",
+        "[detachedPara1.firstChild, 0]",
+        "[detachedPara1.firstChild, 1]",
+        "[detachedPara1.firstChild, 8]",
+        "[detachedPara1.firstChild, 9]",
+        "[foreignPara1.firstChild, 0]",
+        "[foreignPara1.firstChild, 1]",
+        "[foreignPara1.firstChild, 8]",
+        "[foreignPara1.firstChild, 9]",
+        // Now try testing some elements, not just text nodes.
+        "[document.documentElement, -1]",
+        "[document.documentElement, 0]",
+        "[document.documentElement, 1]",
+        "[document.documentElement, 2]",
+        "[document.documentElement, 7]",
+        "[document.head, 1]",
+        "[document.body, 3]",
+        "[foreignDoc.documentElement, 0]",
+        "[foreignDoc.documentElement, 1]",
+        "[foreignDoc.head, 0]",
+        "[foreignDoc.body, 1]",
+        "[paras[0], 0]",
+        "[paras[0], 1]",
+        "[paras[0], 2]",
+        "[paras[1], 0]",
+        "[paras[1], 1]",
+        "[paras[1], 2]",
+        "[detachedPara1, 0]",
+        "[detachedPara1, 1]",
+        "[testDiv, 0]",
+        "[testDiv, 3]",
+        // Then a few more interesting things just for good measure.
+        "[document, -1]",
+        "[document, 0]",
+        "[document, 1]",
+        "[document, 2]",
+        "[document, 3]",
+        "[comment, -1]",
+        "[comment, 0]",
+        "[comment, 4]",
+        "[comment, 96]",
+        "[foreignDoc, 0]",
+        "[foreignDoc, 1]",
+        "[foreignComment, 2]",
+        "[foreignTextNode, 0]",
+        "[foreignTextNode, 36]",
+        "[xmlDoc, -1]",
+        "[xmlDoc, 0]",
+        "[xmlDoc, 1]",
+        "[xmlDoc, 5]",
+        "[xmlComment, 0]",
+        "[xmlComment, 4]",
+        "[processingInstruction, 0]",
+        "[processingInstruction, 5]",
+        "[processingInstruction, 9]",
+        "[detachedTextNode, 0]",
+        "[detachedTextNode, 8]",
+        "[detachedForeignTextNode, 0]",
+        "[detachedForeignTextNode, 8]",
+        "[detachedXmlTextNode, 0]",
+        "[detachedXmlTextNode, 8]",
+        "[detachedProcessingInstruction, 12]",
+        "[detachedComment, 3]",
+        "[detachedComment, 5]",
+        "[detachedForeignComment, 0]",
+        "[detachedForeignComment, 4]",
+        "[detachedXmlComment, 2]",
+        "[docfrag, 0]",
+        "[foreignDocfrag, 0]",
+        "[xmlDocfrag, 0]",
+        "[doctype, 0]",
+        "[doctype, -17]",
+        "[doctype, 1]",
+        "[foreignDoctype, 0]",
+        "[xmlDoctype, 0]",
+    ];
+
+    testNodesShort = [
+        "paras[0]",
+        "paras[0].firstChild",
+        "paras[1].firstChild",
+        "paras[5].firstChild",
+        "foreignPara1",
+        "foreignPara1.firstChild",
+        "detachedPara1",
+        "detachedPara1.firstChild",
+        "document",
+        "detachedDiv",
+        "foreignDoc",
+        "foreignPara2",
+        "xmlDoc",
+        "xmlElement",
+        "detachedTextNode",
+        "foreignTextNode",
+        "processingInstruction",
+        "detachedProcessingInstruction",
+        "comment",
+        "detachedComment",
+        "docfrag",
+        "doctype",
+        "foreignDoctype",
+    ];
+
+    testNodes = testNodesShort.concat([
+        "paras[1]",
+        "detachedPara2",
+        "detachedPara2.firstChild",
+        "testDiv",
+        "detachedXmlElement",
+        "detachedForeignTextNode",
+        "xmlTextNode",
+        "detachedXmlTextNode",
+        "xmlComment",
+        "foreignComment",
+        "detachedForeignComment",
+        "detachedXmlComment",
+        "foreignDocfrag",
+        "xmlDocfrag",
+        "xmlDoctype",
+    ]);
+}
+if ("setup" in window) {
+    setup(setupRangeTests);
+} else {
+    // Presumably we're running from within an iframe or something
+    setupRangeTests();
+}
+
+/**
+ * The "length" of a node as defined by DOM.
+ */
+function nodeLength(node) {
+    // "The length of a node node depends on node:
+    //
+    // "DocumentType
+    //   "0."
+    if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
+        return 0;
+    }
+    // "If node is a CharacterData node, then return node’s data’s length."
+    // Browsers don't historically support the length attribute on
+    // ProcessingInstruction, so to avoid spurious failures, do
+    // node.data.length instead of node.length.
+    if (isText(node) || node.nodeType == Node.PROCESSING_INSTRUCTION_NODE || node.nodeType == Node.COMMENT_NODE) {
+        return node.data.length;
+    }
+    // "Any other node
+    //   "Its number of children."
+    return node.childNodes.length;
+}
+
+/**
+ * Returns the furthest ancestor of a Node as defined by the spec.
+ */
+function furthestAncestor(node) {
+    var root = node;
+    while (root.parentNode != null) {
+        root = root.parentNode;
+    }
+    return root;
+}
+
+/**
+ * "The ancestor containers of a Node are the Node itself and all its
+ * ancestors."
+ *
+ * Is node1 an ancestor container of node2?
+ */
+function isAncestorContainer(node1, node2) {
+    return node1 == node2 ||
+        (node2.compareDocumentPosition(node1) & Node.DOCUMENT_POSITION_CONTAINS);
+}
+
+/**
+ * Returns the first Node that's after node in tree order, or null if node is
+ * the last Node.
+ */
+function nextNode(node) {
+    if (node.hasChildNodes()) {
+        return node.firstChild;
+    }
+    return nextNodeDescendants(node);
+}
+
+/**
+ * Returns the last Node that's before node in tree order, or null if node is
+ * the first Node.
+ */
+function previousNode(node) {
+    if (node.previousSibling) {
+        node = node.previousSibling;
+        while (node.hasChildNodes()) {
+            node = node.lastChild;
+        }
+        return node;
+    }
+    return node.parentNode;
+}
+
+/**
+ * Returns the next Node that's after node and all its descendants in tree
+ * order, or null if node is the last Node or an ancestor of it.
+ */
+function nextNodeDescendants(node) {
+    while (node && !node.nextSibling) {
+        node = node.parentNode;
+    }
+    if (!node) {
+        return null;
+    }
+    return node.nextSibling;
+}
+
+/**
+ * Returns the ownerDocument of the Node, or the Node itself if it's a
+ * Document.
+ */
+function ownerDocument(node) {
+    return node.nodeType == Node.DOCUMENT_NODE
+        ? node
+        : node.ownerDocument;
+}
+
+/**
+ * Returns true if ancestor is an ancestor of descendant, false otherwise.
+ */
+function isAncestor(ancestor, descendant) {
+    if (!ancestor || !descendant) {
+        return false;
+    }
+    while (descendant && descendant != ancestor) {
+        descendant = descendant.parentNode;
+    }
+    return descendant == ancestor;
+}
+
+/**
+ * Returns true if ancestor is an inclusive ancestor of descendant, false
+ * otherwise.
+ */
+function isInclusiveAncestor(ancestor, descendant) {
+    return ancestor === descendant || isAncestor(ancestor, descendant);
+}
+
+/**
+ * Returns true if descendant is a descendant of ancestor, false otherwise.
+ */
+function isDescendant(descendant, ancestor) {
+    return isAncestor(ancestor, descendant);
+}
+
+/**
+ * Returns true if descendant is an inclusive descendant of ancestor, false
+ * otherwise.
+ */
+function isInclusiveDescendant(descendant, ancestor) {
+    return descendant === ancestor || isDescendant(descendant, ancestor);
+}
+
+/**
+ * The position of two boundary points relative to one another, as defined by
+ * the spec.
+ */
+function getPosition(nodeA, offsetA, nodeB, offsetB) {
+    // "If node A is the same as node B, return equal if offset A equals offset
+    // B, before if offset A is less than offset B, and after if offset A is
+    // greater than offset B."
+    if (nodeA == nodeB) {
+        if (offsetA == offsetB) {
+            return "equal";
+        }
+        if (offsetA < offsetB) {
+            return "before";
+        }
+        if (offsetA > offsetB) {
+            return "after";
+        }
+    }
+
+    // "If node A is after node B in tree order, compute the position of (node
+    // B, offset B) relative to (node A, offset A). If it is before, return
+    // after. If it is after, return before."
+    if (nodeB.compareDocumentPosition(nodeA) & Node.DOCUMENT_POSITION_FOLLOWING) {
+        var pos = getPosition(nodeB, offsetB, nodeA, offsetA);
+        if (pos == "before") {
+            return "after";
+        }
+        if (pos == "after") {
+            return "before";
+        }
+    }
+
+    // "If node A is an ancestor of node B:"
+    if (nodeB.compareDocumentPosition(nodeA) & Node.DOCUMENT_POSITION_CONTAINS) {
+        // "Let child equal node B."
+        var child = nodeB;
+
+        // "While child is not a child of node A, set child to its parent."
+        while (child.parentNode != nodeA) {
+            child = child.parentNode;
+        }
+
+        // "If the index of child is less than offset A, return after."
+        if (indexOf(child) < offsetA) {
+            return "after";
+        }
+    }
+
+    // "Return before."
+    return "before";
+}
+
+/**
+ * "contained" as defined by DOM Range: "A Node node is contained in a range
+ * range if node's furthest ancestor is the same as range's root, and (node, 0)
+ * is after range's start, and (node, length of node) is before range's end."
+ */
+function isContained(node, range) {
+    var pos1 = getPosition(node, 0, range.startContainer, range.startOffset);
+    var pos2 = getPosition(node, nodeLength(node), range.endContainer, range.endOffset);
+
+    return furthestAncestor(node) == furthestAncestor(range.startContainer)
+        && pos1 == "after"
+        && pos2 == "before";
+}
+
+/**
+ * "partially contained" as defined by DOM Range: "A Node is partially
+ * contained in a range if it is an ancestor container of the range's start but
+ * not its end, or vice versa."
+ */
+function isPartiallyContained(node, range) {
+    var cond1 = isAncestorContainer(node, range.startContainer);
+    var cond2 = isAncestorContainer(node, range.endContainer);
+    return (cond1 && !cond2) || (cond2 && !cond1);
+}
+
+/**
+ * Index of a node as defined by the spec.
+ */
+function indexOf(node) {
+    if (!node.parentNode) {
+        // No preceding sibling nodes, right?
+        return 0;
+    }
+    var i = 0;
+    while (node != node.parentNode.childNodes[i]) {
+        i++;
+    }
+    return i;
+}
+
+/**
+ * extractContents() implementation, following the spec.  If an exception is
+ * supposed to be thrown, will return a string with the name (e.g.,
+ * "HIERARCHY_REQUEST_ERR") instead of a document fragment.  It might also
+ * return an arbitrary human-readable string if a condition is hit that implies
+ * a spec bug.
+ */
+function myExtractContents(range) {
+    // "Let frag be a new DocumentFragment whose ownerDocument is the same as
+    // the ownerDocument of the context object's start node."
+    var ownerDoc = range.startContainer.nodeType == Node.DOCUMENT_NODE
+        ? range.startContainer
+        : range.startContainer.ownerDocument;
+    var frag = ownerDoc.createDocumentFragment();
+
+    // "If the context object's start and end are the same, abort this method,
+    // returning frag."
+    if (range.startContainer == range.endContainer
+    && range.startOffset == range.endOffset) {
+        return frag;
+    }
+
+    // "Let original start node, original start offset, original end node, and
+    // original end offset be the context object's start and end nodes and
+    // offsets, respectively."
+    var originalStartNode = range.startContainer;
+    var originalStartOffset = range.startOffset;
+    var originalEndNode = range.endContainer;
+    var originalEndOffset = range.endOffset;
+
+    // "If original start node is original end node and it is a CharacterData node, then:"
+    if (range.startContainer == range.endContainer
+    && (isText(range.startContainer)
+    || range.startContainer.nodeType == Node.PROCESSING_INSTRUCTION_NODE
+    || range.startContainer.nodeType == Node.COMMENT_NODE)) {
+        // "Let clone be the result of calling cloneNode(false) on original
+        // start node."
+        var clone = originalStartNode.cloneNode(false);
+
+        // "Set the data of clone to the result of calling
+        // substringData(original start offset, original end offset − original
+        // start offset) on original start node."
+        clone.data = originalStartNode.substringData(originalStartOffset,
+            originalEndOffset - originalStartOffset);
+
+        // "Append clone as the last child of frag."
+        frag.appendChild(clone);
+
+        // "Call deleteData(original start offset, original end offset −
+        // original start offset) on original start node."
+        originalStartNode.deleteData(originalStartOffset,
+            originalEndOffset - originalStartOffset);
+
+        // "Abort this method, returning frag."
+        return frag;
+    }
+
+    // "Let common ancestor equal original start node."
+    var commonAncestor = originalStartNode;
+
+    // "While common ancestor is not an ancestor container of original end
+    // node, set common ancestor to its own parent."
+    while (!isAncestorContainer(commonAncestor, originalEndNode)) {
+        commonAncestor = commonAncestor.parentNode;
+    }
+
+    // "If original start node is an ancestor container of original end node,
+    // let first partially contained child be null."
+    var firstPartiallyContainedChild;
+    if (isAncestorContainer(originalStartNode, originalEndNode)) {
+        firstPartiallyContainedChild = null;
+    // "Otherwise, let first partially contained child be the first child of
+    // common ancestor that is partially contained in the context object."
+    } else {
+        for (var i = 0; i < commonAncestor.childNodes.length; i++) {
+            if (isPartiallyContained(commonAncestor.childNodes[i], range)) {
+                firstPartiallyContainedChild = commonAncestor.childNodes[i];
+                break;
+            }
+        }
+        if (!firstPartiallyContainedChild) {
+            throw "Spec bug: no first partially contained child!";
+        }
+    }
+
+    // "If original end node is an ancestor container of original start node,
+    // let last partially contained child be null."
+    var lastPartiallyContainedChild;
+    if (isAncestorContainer(originalEndNode, originalStartNode)) {
+        lastPartiallyContainedChild = null;
+    // "Otherwise, let last partially contained child be the last child of
+    // common ancestor that is partially contained in the context object."
+    } else {
+        for (var i = commonAncestor.childNodes.length - 1; i >= 0; i--) {
+            if (isPartiallyContained(commonAncestor.childNodes[i], range)) {
+                lastPartiallyContainedChild = commonAncestor.childNodes[i];
+                break;
+            }
+        }
+        if (!lastPartiallyContainedChild) {
+            throw "Spec bug: no last partially contained child!";
+        }
+    }
+
+    // "Let contained children be a list of all children of common ancestor
+    // that are contained in the context object, in tree order."
+    //
+    // "If any member of contained children is a DocumentType, raise a
+    // HIERARCHY_REQUEST_ERR exception and abort these steps."
+    var containedChildren = [];
+    for (var i = 0; i < commonAncestor.childNodes.length; i++) {
+        if (isContained(commonAncestor.childNodes[i], range)) {
+            if (commonAncestor.childNodes[i].nodeType
+            == Node.DOCUMENT_TYPE_NODE) {
+                return "HIERARCHY_REQUEST_ERR";
+            }
+            containedChildren.push(commonAncestor.childNodes[i]);
+        }
+    }
+
+    // "If original start node is an ancestor container of original end node,
+    // set new node to original start node and new offset to original start
+    // offset."
+    var newNode, newOffset;
+    if (isAncestorContainer(originalStartNode, originalEndNode)) {
+        newNode = originalStartNode;
+        newOffset = originalStartOffset;
+    // "Otherwise:"
+    } else {
+        // "Let reference node equal original start node."
+        var referenceNode = originalStartNode;
+
+        // "While reference node's parent is not null and is not an ancestor
+        // container of original end node, set reference node to its parent."
+        while (referenceNode.parentNode
+        && !isAncestorContainer(referenceNode.parentNode, originalEndNode)) {
+            referenceNode = referenceNode.parentNode;
+        }
+
+        // "Set new node to the parent of reference node, and new offset to one
+        // plus the index of reference node."
+        newNode = referenceNode.parentNode;
+        newOffset = 1 + indexOf(referenceNode);
+    }
+
+    // "If first partially contained child is a CharacterData node, then:"
+    if (firstPartiallyContainedChild
+    && (isText(firstPartiallyContainedChild)
+    || firstPartiallyContainedChild.nodeType == Node.PROCESSING_INSTRUCTION_NODE
+    || firstPartiallyContainedChild.nodeType == Node.COMMENT_NODE)) {
+        // "Let clone be the result of calling cloneNode(false) on original
+        // start node."
+        var clone = originalStartNode.cloneNode(false);
+
+        // "Set the data of clone to the result of calling substringData() on
+        // original start node, with original start offset as the first
+        // argument and (length of original start node − original start offset)
+        // as the second."
+        clone.data = originalStartNode.substringData(originalStartOffset,
+            nodeLength(originalStartNode) - originalStartOffset);
+
+        // "Append clone as the last child of frag."
+        frag.appendChild(clone);
+
+        // "Call deleteData() on original start node, with original start
+        // offset as the first argument and (length of original start node −
+        // original start offset) as the second."
+        originalStartNode.deleteData(originalStartOffset,
+            nodeLength(originalStartNode) - originalStartOffset);
+    // "Otherwise, if first partially contained child is not null:"
+    } else if (firstPartiallyContainedChild) {
+        // "Let clone be the result of calling cloneNode(false) on first
+        // partially contained child."
+        var clone = firstPartiallyContainedChild.cloneNode(false);
+
+        // "Append clone as the last child of frag."
+        frag.appendChild(clone);
+
+        // "Let subrange be a new Range whose start is (original start node,
+        // original start offset) and whose end is (first partially contained
+        // child, length of first partially contained child)."
+        var subrange = ownerDoc.createRange();
+        subrange.setStart(originalStartNode, originalStartOffset);
+        subrange.setEnd(firstPartiallyContainedChild,
+            nodeLength(firstPartiallyContainedChild));
+
+        // "Let subfrag be the result of calling extractContents() on
+        // subrange."
+        var subfrag = myExtractContents(subrange);
+
+        // "For each child of subfrag, in order, append that child to clone as
+        // its last child."
+        for (var i = 0; i < subfrag.childNodes.length; i++) {
+            clone.appendChild(subfrag.childNodes[i]);
+        }
+    }
+
+    // "For each contained child in contained children, append contained child
+    // as the last child of frag."
+    for (var i = 0; i < containedChildren.length; i++) {
+        frag.appendChild(containedChildren[i]);
+    }
+
+    // "If last partially contained child is a CharacterData node, then:"
+    if (lastPartiallyContainedChild
+    && (isText(lastPartiallyContainedChild)
+    || lastPartiallyContainedChild.nodeType == Node.PROCESSING_INSTRUCTION_NODE
+    || lastPartiallyContainedChild.nodeType == Node.COMMENT_NODE)) {
+        // "Let clone be the result of calling cloneNode(false) on original
+        // end node."
+        var clone = originalEndNode.cloneNode(false);
+
+        // "Set the data of clone to the result of calling substringData(0,
+        // original end offset) on original end node."
+        clone.data = originalEndNode.substringData(0, originalEndOffset);
+
+        // "Append clone as the last child of frag."
+        frag.appendChild(clone);
+
+        // "Call deleteData(0, original end offset) on original end node."
+        originalEndNode.deleteData(0, originalEndOffset);
+    // "Otherwise, if last partially contained child is not null:"
+    } else if (lastPartiallyContainedChild) {
+        // "Let clone be the result of calling cloneNode(false) on last
+        // partially contained child."
+        var clone = lastPartiallyContainedChild.cloneNode(false);
+
+        // "Append clone as the last child of frag."
+        frag.appendChild(clone);
+
+        // "Let subrange be a new Range whose start is (last partially
+        // contained child, 0) and whose end is (original end node, original
+        // end offset)."
+        var subrange = ownerDoc.createRange();
+        subrange.setStart(lastPartiallyContainedChild, 0);
+        subrange.setEnd(originalEndNode, originalEndOffset);
+
+        // "Let subfrag be the result of calling extractContents() on
+        // subrange."
+        var subfrag = myExtractContents(subrange);
+
+        // "For each child of subfrag, in order, append that child to clone as
+        // its last child."
+        for (var i = 0; i < subfrag.childNodes.length; i++) {
+            clone.appendChild(subfrag.childNodes[i]);
+        }
+    }
+
+    // "Set the context object's start and end to (new node, new offset)."
+    range.setStart(newNode, newOffset);
+    range.setEnd(newNode, newOffset);
+
+    // "Return frag."
+    return frag;
+}
+
+/**
+ * insertNode() implementation, following the spec.  If an exception is meant
+ * to be thrown, will return a string with the expected exception name, for
+ * instance "HIERARCHY_REQUEST_ERR".
+ */
+function myInsertNode(range, node) {
+    // "If range's start node is a ProcessingInstruction or Comment node, or is
+    // a Text node whose parent is null, or is node, throw an
+    // "HierarchyRequestError" exception and terminate these steps."
+    if (range.startContainer.nodeType == Node.PROCESSING_INSTRUCTION_NODE
+            || range.startContainer.nodeType == Node.COMMENT_NODE
+            || (isText(range.startContainer)
+                && !range.startContainer.parentNode)
+            || range.startContainer == node) {
+                    return "HIERARCHY_REQUEST_ERR";
+    }
+
+    // "Let referenceNode be null."
+    var referenceNode = null;
+
+    // "If range's start node is a Text node, set referenceNode to that Text node."
+    if (isText(range.startContainer)) {
+        referenceNode = range.startContainer;
+
+        // "Otherwise, set referenceNode to the child of start node whose index is
+        // start offset, and null if there is no such child."
+    } else {
+        if (range.startOffset < range.startContainer.childNodes.length) {
+            referenceNode = range.startContainer.childNodes[range.startOffset];
+        } else {
+            referenceNode = null;
+        }
+    }
+
+    // "Let parent be range's start node if referenceNode is null, and
+    // referenceNode's parent otherwise."
+    var parent_ = referenceNode === null ? range.startContainer :
+        referenceNode.parentNode;
+
+    // "Ensure pre-insertion validity of node into parent before
+    // referenceNode."
+    var error = ensurePreInsertionValidity(node, parent_, referenceNode);
+    if (error) {
+        return error;
+    }
+
+    // "If range's start node is a Text node, set referenceNode to the result
+    // of splitting it with offset range's start offset."
+    if (isText(range.startContainer)) {
+        referenceNode = range.startContainer.splitText(range.startOffset);
+    }
+
+    // "If node is referenceNode, set referenceNode to its next sibling."
+    if (node == referenceNode) {
+        referenceNode = referenceNode.nextSibling;
+    }
+
+    // "If node's parent is not null, remove node from its parent."
+    if (node.parentNode) {
+        node.parentNode.removeChild(node);
+    }
+
+    // "Let newOffset be parent's length if referenceNode is null, and
+    // referenceNode's index otherwise."
+    var newOffset = referenceNode === null ? nodeLength(parent_) :
+        indexOf(referenceNode);
+
+    // "Increase newOffset by node's length if node is a DocumentFragment node,
+    // and one otherwise."
+    newOffset += node.nodeType == Node.DOCUMENT_FRAGMENT_NODE ?
+        nodeLength(node) : 1;
+
+    // "Pre-insert node into parent before referenceNode."
+    parent_.insertBefore(node, referenceNode);
+
+    // "If range's start and end are the same, set range's end to (parent,
+    // newOffset)."
+    if (range.startContainer == range.endContainer
+    && range.startOffset == range.endOffset) {
+        range.setEnd(parent_, newOffset);
+    }
+}
+
+// To make filter() calls more readable
+function isElement(node) {
+    return node.nodeType == Node.ELEMENT_NODE;
+}
+
+function isText(node) {
+    return node.nodeType == Node.TEXT_NODE || node.nodeType == Node.CDATA_SECTION_NODE;
+}
+
+function isDoctype(node) {
+    return node.nodeType == Node.DOCUMENT_TYPE_NODE;
+}
+
+function ensurePreInsertionValidity(node, parent_, child) {
+    // "If parent is not a Document, DocumentFragment, or Element node, throw a
+    // HierarchyRequestError."
+    if (parent_.nodeType != Node.DOCUMENT_NODE
+            && parent_.nodeType != Node.DOCUMENT_FRAGMENT_NODE
+            && parent_.nodeType != Node.ELEMENT_NODE) {
+                return "HIERARCHY_REQUEST_ERR";
+    }
+
+    // "If node is a host-including inclusive ancestor of parent, throw a
+    // HierarchyRequestError."
+    //
+    // XXX Does not account for host
+    if (isInclusiveAncestor(node, parent_)) {
+        return "HIERARCHY_REQUEST_ERR";
+    }
+
+    // "If child is not null and its parent is not parent, throw a NotFoundError
+    // exception."
+    if (child && child.parentNode != parent_) {
+        return "NOT_FOUND_ERR";
+    }
+
+    // "If node is not a DocumentFragment, DocumentType, Element, or CharacterData node,
+    // then throw a HierarchyRequestError."
+    if (node.nodeType != Node.DOCUMENT_FRAGMENT_NODE
+            && node.nodeType != Node.DOCUMENT_TYPE_NODE
+            && node.nodeType != Node.ELEMENT_NODE
+            && !isText(node)
+            && node.nodeType != Node.PROCESSING_INSTRUCTION_NODE
+            && node.nodeType != Node.COMMENT_NODE) {
+                return "HIERARCHY_REQUEST_ERR";
+    }
+
+    // "If either node is a Text node and parent is a document, or node is a
+    // doctype and parent is not a document, throw a HierarchyRequestError."
+    if ((isText(node)
+                && parent_.nodeType == Node.DOCUMENT_NODE)
+            || (node.nodeType == Node.DOCUMENT_TYPE_NODE
+                && parent_.nodeType != Node.DOCUMENT_NODE)) {
+                    return "HIERARCHY_REQUEST_ERR";
+    }
+
+    // "If parent is a document, and any of the statements below, switched on
+    // node, are true, throw a HierarchyRequestError."
+    if (parent_.nodeType == Node.DOCUMENT_NODE) {
+        switch (node.nodeType) {
+        case Node.DOCUMENT_FRAGMENT_NODE:
+            // "If node has more than one element child or has a Text node
+            // child.  Otherwise, if node has one element child and either
+            // parent has an element child, child is a doctype, or child is not
+            // null and a doctype is following child."
+            if ([].filter.call(node.childNodes, isElement).length > 1) {
+                return "HIERARCHY_REQUEST_ERR";
+            }
+
+            if ([].some.call(node.childNodes, isText)) {
+                return "HIERARCHY_REQUEST_ERR";
+            }
+
+            if ([].filter.call(node.childNodes, isElement).length == 1) {
+                if ([].some.call(parent_.childNodes, isElement)) {
+                    return "HIERARCHY_REQUEST_ERR";
+                }
+
+                if (child && child.nodeType == Node.DOCUMENT_TYPE_NODE) {
+                    return "HIERARCHY_REQUEST_ERR";
+                }
+
+                if (child && [].slice.call(parent_.childNodes, indexOf(child) + 1)
+                               .filter(isDoctype)) {
+                    return "HIERARCHY_REQUEST_ERR";
+                }
+            }
+            break;
+
+        case Node.ELEMENT_NODE:
+            // "parent has an element child, child is a doctype, or child is
+            // not null and a doctype is following child."
+            if ([].some.call(parent_.childNodes, isElement)) {
+                return "HIERARCHY_REQUEST_ERR";
+            }
+
+            if (child.nodeType == Node.DOCUMENT_TYPE_NODE) {
+                return "HIERARCHY_REQUEST_ERR";
+            }
+
+            if (child && [].slice.call(parent_.childNodes, indexOf(child) + 1)
+                           .filter(isDoctype)) {
+                return "HIERARCHY_REQUEST_ERR";
+            }
+            break;
+
+        case Node.DOCUMENT_TYPE_NODE:
+            // "parent has a doctype child, an element is preceding child, or
+            // child is null and parent has an element child."
+            if ([].some.call(parent_.childNodes, isDoctype)) {
+                return "HIERARCHY_REQUEST_ERR";
+            }
+
+            if (child && [].slice.call(parent_.childNodes, 0, indexOf(child))
+                           .some(isElement)) {
+                return "HIERARCHY_REQUEST_ERR";
+            }
+
+            if (!child && [].some.call(parent_.childNodes, isElement)) {
+                return "HIERARCHY_REQUEST_ERR";
+            }
+            break;
+        }
+    }
+}
+
+/**
+ * Asserts that two nodes are equal, in the sense of isEqualNode().  If they
+ * aren't, tries to print a relatively informative reason why not.  TODO: Move
+ * this to testharness.js?
+ */
+function assertNodesEqual(actual, expected, msg) {
+    if (!actual.isEqualNode(expected)) {
+        msg = "Actual and expected mismatch for " + msg + ".  ";
+
+        while (actual && expected) {
+            assert_true(actual.nodeType === expected.nodeType
+                && actual.nodeName === expected.nodeName
+                && actual.nodeValue === expected.nodeValue,
+                "First differing node: expected " + format_value(expected)
+                + ", got " + format_value(actual) + " [" + msg + "]");
+            actual = nextNode(actual);
+            expected = nextNode(expected);
+        }
+
+        assert_unreached("DOMs were not equal but we couldn't figure out why");
+    }
+}
+
+/**
+ * Given a DOMException, return the name (e.g., "HIERARCHY_REQUEST_ERR").
+ */
+function getDomExceptionName(e) {
+    var ret = null;
+    for (var prop in e) {
+        if (/^[A-Z_]+_ERR$/.test(prop) && e[prop] == e.code) {
+            return prop;
+        }
+    }
+
+    throw "Exception seems to not be a DOMException?  " + e;
+}
+
+/**
+ * Given an array of endpoint data [start container, start offset, end
+ * container, end offset], returns a Range with those endpoints.
+ */
+function rangeFromEndpoints(endpoints) {
+    // If we just use document instead of the ownerDocument of endpoints[0],
+    // WebKit will throw on setStart/setEnd.  This is a WebKit bug, but it's in
+    // range, not selection, so we don't want to fail anything for it.
+    var range = ownerDocument(endpoints[0]).createRange();
+    range.setStart(endpoints[0], endpoints[1]);
+    range.setEnd(endpoints[2], endpoints[3]);
+    return range;
+}

+ 710 - 0
Tests/LibWeb/Text/input/wpt-import/dom/nodes/Node-properties.html

@@ -0,0 +1,710 @@
+<!doctype html>
+<title>Node assorted property tests</title>
+<link rel=author title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta charset=utf-8>
+<div id=log></div>
+<script src=../../resources/testharness.js></script>
+<script src=../../resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+/**
+ * First we define a data structure to tell us what tests to run.  The keys
+ * will be eval()ed, and are mostly global variables defined in common.js.  The
+ * values are objects, which maps properties to expected values.  So
+ *
+ *     foo: {
+ *         bar: "baz",
+ *         quz: 7,
+ *     },
+ *
+ * will test that eval("foo.bar") === "baz" and eval("foo.quz") === 7.  "foo"
+ * and "bar" could thus be expressions, like "document.documentElement" and
+ * "childNodes[4]" respectively.
+ *
+ * To avoid repetition, some values are automatically added based on others.
+ * For instance, if we specify nodeType: Node.TEXT_NODE, we'll automatically
+ * also test nodeName: "#text".  This is handled by code after this variable is
+ * defined.
+ */
+var expected = {
+    testDiv: {
+        // Node
+        nodeType: Node.ELEMENT_NODE,
+        ownerDocument: document,
+        parentNode: document.body,
+        parentElement: document.body,
+        "childNodes.length": 7,
+        "childNodes[0]": paras[0],
+        "childNodes[1]": paras[1],
+        "childNodes[2]": paras[2],
+        "childNodes[3]": paras[3],
+        "childNodes[4]": paras[4],
+        "childNodes[5]": paras[5],
+        "childNodes[6]": comment,
+        previousSibling: null,
+        nextSibling: document.getElementById("log"),
+        textContent: "A\u0308b\u0308c\u0308d\u0308e\u0308f\u0308g\u0308h\u0308\nIjklmnop\nQrstuvwxYzabcdefGhijklmn123456789012",
+
+        // Element
+        namespaceURI: "http://www.w3.org/1999/xhtml",
+        prefix: null,
+        localName: "div",
+        tagName: "DIV",
+        id: "test",
+        "children[0]": paras[0],
+        "children[1]": paras[1],
+        "children[2]": paras[2],
+        "children[3]": paras[3],
+        "children[4]": paras[4],
+        "children[5]": paras[5],
+        previousElementSibling: null,
+        // nextSibling isn't explicitly set
+        //nextElementSibling: ,
+        childElementCount: 6,
+    },
+    detachedDiv: {
+        // Node
+        nodeType: Node.ELEMENT_NODE,
+        ownerDocument: document,
+        parentNode: null,
+        parentElement: null,
+        "childNodes.length": 2,
+        "childNodes[0]": detachedPara1,
+        "childNodes[1]": detachedPara2,
+        previousSibling: null,
+        nextSibling: null,
+        textContent: "OpqrstuvWxyzabcd",
+
+        // Element
+        namespaceURI: "http://www.w3.org/1999/xhtml",
+        prefix: null,
+        localName: "div",
+        tagName: "DIV",
+        "children[0]": detachedPara1,
+        "children[1]": detachedPara2,
+        previousElementSibling: null,
+        nextElementSibling: null,
+        childElementCount: 2,
+    },
+    detachedPara1: {
+        // Node
+        nodeType: Node.ELEMENT_NODE,
+        ownerDocument: document,
+        parentNode: detachedDiv,
+        parentElement: detachedDiv,
+        "childNodes.length": 1,
+        previousSibling: null,
+        nextSibling: detachedPara2,
+        textContent: "Opqrstuv",
+
+        // Element
+        namespaceURI: "http://www.w3.org/1999/xhtml",
+        prefix: null,
+        localName: "p",
+        tagName: "P",
+        previousElementSibling: null,
+        nextElementSibling: detachedPara2,
+        childElementCount: 0,
+    },
+    detachedPara2: {
+        // Node
+        nodeType: Node.ELEMENT_NODE,
+        ownerDocument: document,
+        parentNode: detachedDiv,
+        parentElement: detachedDiv,
+        "childNodes.length": 1,
+        previousSibling: detachedPara1,
+        nextSibling: null,
+        textContent: "Wxyzabcd",
+
+        // Element
+        namespaceURI: "http://www.w3.org/1999/xhtml",
+        prefix: null,
+        localName: "p",
+        tagName: "P",
+        previousElementSibling: detachedPara1,
+        nextElementSibling: null,
+        childElementCount: 0,
+    },
+    document: {
+        // Node
+        nodeType: Node.DOCUMENT_NODE,
+        "childNodes.length": 2,
+        "childNodes[0]": document.doctype,
+        "childNodes[1]": document.documentElement,
+
+        // Document
+        URL: String(location),
+        compatMode: "CSS1Compat",
+        characterSet: "UTF-8",
+        contentType: "text/html",
+        doctype: doctype,
+        //documentElement: ,
+    },
+    foreignDoc: {
+        // Node
+        nodeType: Node.DOCUMENT_NODE,
+        "childNodes.length": 3,
+        "childNodes[0]": foreignDoc.doctype,
+        "childNodes[1]": foreignDoc.documentElement,
+        "childNodes[2]": foreignComment,
+
+        // Document
+        URL: "about:blank",
+        compatMode: "CSS1Compat",
+        characterSet: "UTF-8",
+        contentType: "text/html",
+        //doctype: ,
+        //documentElement: ,
+    },
+    foreignPara1: {
+        // Node
+        nodeType: Node.ELEMENT_NODE,
+        ownerDocument: foreignDoc,
+        parentNode: foreignDoc.body,
+        parentElement: foreignDoc.body,
+        "childNodes.length": 1,
+        previousSibling: null,
+        nextSibling: foreignPara2,
+        textContent: "Efghijkl",
+
+        // Element
+        namespaceURI: "http://www.w3.org/1999/xhtml",
+        prefix: null,
+        localName: "p",
+        tagName: "P",
+        previousElementSibling: null,
+        nextElementSibling: foreignPara2,
+        childElementCount: 0,
+    },
+    foreignPara2: {
+        // Node
+        nodeType: Node.ELEMENT_NODE,
+        ownerDocument: foreignDoc,
+        parentNode: foreignDoc.body,
+        parentElement: foreignDoc.body,
+        "childNodes.length": 1,
+        previousSibling: foreignPara1,
+        nextSibling: foreignTextNode,
+        textContent: "Mnopqrst",
+
+        // Element
+        namespaceURI: "http://www.w3.org/1999/xhtml",
+        prefix: null,
+        localName: "p",
+        tagName: "P",
+        previousElementSibling: foreignPara1,
+        nextElementSibling: null,
+        childElementCount: 0,
+    },
+    xmlDoc: {
+        // Node
+        nodeType: Node.DOCUMENT_NODE,
+        "childNodes.length": 4,
+        "childNodes[0]": xmlDoctype,
+        "childNodes[1]": xmlElement,
+        "childNodes[2]": processingInstruction,
+        "childNodes[3]": xmlComment,
+
+        // Document
+        URL: "about:blank",
+        compatMode: "CSS1Compat",
+        characterSet: "UTF-8",
+        contentType: "application/xml",
+        //doctype: ,
+        //documentElement: ,
+    },
+    xmlElement: {
+        // Node
+        nodeType: Node.ELEMENT_NODE,
+        ownerDocument: xmlDoc,
+        parentNode: xmlDoc,
+        parentElement: null,
+        "childNodes.length": 1,
+        "childNodes[0]": xmlTextNode,
+        previousSibling: xmlDoctype,
+        nextSibling: processingInstruction,
+        textContent: "do re mi fa so la ti",
+
+        // Element
+        namespaceURI: null,
+        prefix: null,
+        localName: "igiveuponcreativenames",
+        tagName: "igiveuponcreativenames",
+        previousElementSibling: null,
+        nextElementSibling: null,
+        childElementCount: 0,
+    },
+    detachedXmlElement: {
+        // Node
+        nodeType: Node.ELEMENT_NODE,
+        ownerDocument: xmlDoc,
+        parentNode: null,
+        parentElement: null,
+        "childNodes.length": 0,
+        previousSibling: null,
+        nextSibling: null,
+        textContent: "",
+
+        // Element
+        namespaceURI: null,
+        prefix: null,
+        localName: "everyone-hates-hyphenated-element-names",
+        tagName: "everyone-hates-hyphenated-element-names",
+        previousElementSibling: null,
+        nextElementSibling: null,
+        childElementCount: 0,
+    },
+    detachedTextNode: {
+        // Node
+        nodeType: Node.TEXT_NODE,
+        ownerDocument: document,
+        parentNode: null,
+        parentElement: null,
+        previousSibling: null,
+        nextSibling: null,
+        nodeValue: "Uvwxyzab",
+
+        // Text
+        wholeText: "Uvwxyzab",
+    },
+    foreignTextNode: {
+        // Node
+        nodeType: Node.TEXT_NODE,
+        ownerDocument: foreignDoc,
+        parentNode: foreignDoc.body,
+        parentElement: foreignDoc.body,
+        previousSibling: foreignPara2,
+        nextSibling: null,
+        nodeValue: "I admit that I harbor doubts about whether we really need so many things to test, but it's too late to stop now.",
+
+        // Text
+        wholeText: "I admit that I harbor doubts about whether we really need so many things to test, but it's too late to stop now.",
+    },
+    detachedForeignTextNode: {
+        // Node
+        nodeType: Node.TEXT_NODE,
+        ownerDocument: foreignDoc,
+        parentNode: null,
+        parentElement: null,
+        previousSibling: null,
+        nextSibling: null,
+        nodeValue: "Cdefghij",
+
+        // Text
+        wholeText: "Cdefghij",
+    },
+    xmlTextNode: {
+        // Node
+        nodeType: Node.TEXT_NODE,
+        ownerDocument: xmlDoc,
+        parentNode: xmlElement,
+        parentElement: xmlElement,
+        previousSibling: null,
+        nextSibling: null,
+        nodeValue: "do re mi fa so la ti",
+
+        // Text
+        wholeText: "do re mi fa so la ti",
+    },
+    detachedXmlTextNode: {
+        // Node
+        nodeType: Node.TEXT_NODE,
+        ownerDocument: xmlDoc,
+        parentNode: null,
+        parentElement: null,
+        previousSibling: null,
+        nextSibling: null,
+        nodeValue: "Klmnopqr",
+
+        // Text
+        wholeText: "Klmnopqr",
+    },
+    processingInstruction: {
+        // Node
+        nodeType: Node.PROCESSING_INSTRUCTION_NODE,
+        ownerDocument: xmlDoc,
+        parentNode: xmlDoc,
+        parentElement: null,
+        previousSibling: xmlElement,
+        nextSibling: xmlComment,
+        nodeValue: 'Did you know that ":syn sync fromstart" is very useful when using vim to edit large amounts of JavaScript embedded in HTML?',
+
+        // ProcessingInstruction
+        target: "somePI",
+    },
+    detachedProcessingInstruction: {
+        // Node
+        nodeType: Node.PROCESSING_INSTRUCTION_NODE,
+        ownerDocument: xmlDoc,
+        parentNode: null,
+        parentElement: null,
+        previousSibling: null,
+        nextSibling: null,
+        nodeValue: "chirp chirp chirp",
+
+        // ProcessingInstruction
+        target: "whippoorwill",
+    },
+    comment: {
+        // Node
+        nodeType: Node.COMMENT_NODE,
+        ownerDocument: document,
+        parentNode: testDiv,
+        parentElement: testDiv,
+        previousSibling: paras[5],
+        nextSibling: null,
+        nodeValue: "Alphabet soup?",
+    },
+    detachedComment: {
+        // Node
+        nodeType: Node.COMMENT_NODE,
+        ownerDocument: document,
+        parentNode: null,
+        parentElement: null,
+        previousSibling: null,
+        nextSibling: null,
+        nodeValue: "Stuvwxyz",
+    },
+    foreignComment: {
+        // Node
+        nodeType: Node.COMMENT_NODE,
+        ownerDocument: foreignDoc,
+        parentNode: foreignDoc,
+        parentElement: null,
+        previousSibling: foreignDoc.documentElement,
+        nextSibling: null,
+        nodeValue: '"Commenter" and "commentator" mean different things.  I\'ve seen non-native speakers trip up on this.',
+    },
+    detachedForeignComment: {
+        // Node
+        nodeType: Node.COMMENT_NODE,
+        ownerDocument: foreignDoc,
+        parentNode: null,
+        parentElement: null,
+        previousSibling: null,
+        nextSibling: null,
+        nodeValue: "אריה יהודה",
+    },
+    xmlComment: {
+        // Node
+        nodeType: Node.COMMENT_NODE,
+        ownerDocument: xmlDoc,
+        parentNode: xmlDoc,
+        parentElement: null,
+        previousSibling: processingInstruction,
+        nextSibling: null,
+        nodeValue: "I maliciously created a comment that will break incautious XML serializers, but Firefox threw an exception, so all I got was this lousy T-shirt",
+    },
+    detachedXmlComment: {
+        // Node
+        nodeType: Node.COMMENT_NODE,
+        ownerDocument: xmlDoc,
+        parentNode: null,
+        parentElement: null,
+        previousSibling: null,
+        nextSibling: null,
+        nodeValue: "בן חיים אליעזר",
+    },
+    docfrag: {
+        // Node
+        nodeType: Node.DOCUMENT_FRAGMENT_NODE,
+        ownerDocument: document,
+        "childNodes.length": 0,
+        textContent: "",
+    },
+    foreignDocfrag: {
+        // Node
+        nodeType: Node.DOCUMENT_FRAGMENT_NODE,
+        ownerDocument: foreignDoc,
+        "childNodes.length": 0,
+        textContent: "",
+    },
+    xmlDocfrag: {
+        // Node
+        nodeType: Node.DOCUMENT_FRAGMENT_NODE,
+        ownerDocument: xmlDoc,
+        "childNodes.length": 0,
+        textContent: "",
+    },
+    doctype: {
+        // Node
+        nodeType: Node.DOCUMENT_TYPE_NODE,
+        ownerDocument: document,
+        parentNode: document,
+        previousSibling: null,
+        nextSibling: document.documentElement,
+
+        // DocumentType
+        name: "html",
+        publicId: "",
+        systemId: "",
+    },
+    foreignDoctype: {
+        // Node
+        nodeType: Node.DOCUMENT_TYPE_NODE,
+        ownerDocument: foreignDoc,
+        parentNode: foreignDoc,
+        previousSibling: null,
+        nextSibling: foreignDoc.documentElement,
+
+        // DocumentType
+        name: "html",
+        publicId: "",
+        systemId: "",
+    },
+    xmlDoctype: {
+        // Node
+        nodeType: Node.DOCUMENT_TYPE_NODE,
+        ownerDocument: xmlDoc,
+        parentNode: xmlDoc,
+        previousSibling: null,
+        nextSibling: xmlElement,
+
+        // DocumentType
+        name: "qorflesnorf",
+        publicId: "abcde",
+        systemId: "x\"'y",
+    },
+    "paras[0]": {
+        // Node
+        nodeType: Node.ELEMENT_NODE,
+        ownerDocument: document,
+        parentNode: testDiv,
+        parentElement: testDiv,
+        "childNodes.length": 1,
+        previousSibling: null,
+        nextSibling: paras[1],
+        textContent: "A\u0308b\u0308c\u0308d\u0308e\u0308f\u0308g\u0308h\u0308\n",
+
+        // Element
+        namespaceURI: "http://www.w3.org/1999/xhtml",
+        prefix: null,
+        localName: "p",
+        tagName: "P",
+        id: "a",
+        previousElementSibling: null,
+        nextElementSibling: paras[1],
+        childElementCount: 0,
+    },
+    "paras[1]": {
+        // Node
+        nodeType: Node.ELEMENT_NODE,
+        ownerDocument: document,
+        parentNode: testDiv,
+        parentElement: testDiv,
+        "childNodes.length": 1,
+        previousSibling: paras[0],
+        nextSibling: paras[2],
+        textContent: "Ijklmnop\n",
+
+        // Element
+        namespaceURI: "http://www.w3.org/1999/xhtml",
+        prefix: null,
+        localName: "p",
+        tagName: "P",
+        id: "b",
+        previousElementSibling: paras[0],
+        nextElementSibling: paras[2],
+        childElementCount: 0,
+    },
+    "paras[2]": {
+        // Node
+        nodeType: Node.ELEMENT_NODE,
+        ownerDocument: document,
+        parentNode: testDiv,
+        parentElement: testDiv,
+        "childNodes.length": 1,
+        previousSibling: paras[1],
+        nextSibling: paras[3],
+        textContent: "Qrstuvwx",
+
+        // Element
+        namespaceURI: "http://www.w3.org/1999/xhtml",
+        prefix: null,
+        localName: "p",
+        tagName: "P",
+        id: "c",
+        previousElementSibling: paras[1],
+        nextElementSibling: paras[3],
+        childElementCount: 0,
+    },
+    "paras[3]": {
+        // Node
+        nodeType: Node.ELEMENT_NODE,
+        ownerDocument: document,
+        parentNode: testDiv,
+        parentElement: testDiv,
+        "childNodes.length": 1,
+        previousSibling: paras[2],
+        nextSibling: paras[4],
+        textContent: "Yzabcdef",
+
+        // Element
+        namespaceURI: "http://www.w3.org/1999/xhtml",
+        prefix: null,
+        localName: "p",
+        tagName: "P",
+        id: "d",
+        previousElementSibling: paras[2],
+        nextElementSibling: paras[4],
+        childElementCount: 0,
+    },
+    "paras[4]": {
+        // Node
+        nodeType: Node.ELEMENT_NODE,
+        ownerDocument: document,
+        parentNode: testDiv,
+        parentElement: testDiv,
+        "childNodes.length": 1,
+        previousSibling: paras[3],
+        nextSibling: paras[5],
+        textContent: "Ghijklmn",
+
+        // Element
+        namespaceURI: "http://www.w3.org/1999/xhtml",
+        prefix: null,
+        localName: "p",
+        tagName: "P",
+        id: "e",
+        previousElementSibling: paras[3],
+        nextElementSibling: paras[5],
+        childElementCount: 0,
+    },
+    "paras[5]": {
+        // Node
+        nodeType: Node.ELEMENT_NODE,
+        ownerDocument: document,
+        parentNode: testDiv,
+        parentElement: testDiv,
+        "childNodes.length": 3,
+        previousSibling: paras[4],
+        nextSibling: comment,
+        textContent: "123456789012",
+
+        // Element
+        namespaceURI: "http://www.w3.org/1999/xhtml",
+        prefix: null,
+        localName: "p",
+        tagName: "P",
+        previousElementSibling: paras[4],
+        nextElementSibling: null,
+        childElementCount: 0,
+    }
+};
+
+for (var node in expected) {
+    // Now we set various default values by node type.
+    switch (expected[node].nodeType) {
+    case Node.ELEMENT_NODE:
+        expected[node].nodeName = expected[node].tagName;
+        expected[node].nodeValue = null;
+        expected[node]["children.length"] = expected[node].childElementCount;
+
+        if (expected[node].id === undefined) {
+            expected[node].id = "";
+        }
+        if (expected[node].className === undefined) {
+            expected[node].className = "";
+        }
+
+        var len = expected[node].childElementCount;
+        if (len === 0) {
+            expected[node].firstElementChild =
+            expected[node].lastElementChild = null;
+        } else {
+            // If we have expectations for the first/last child in children,
+            // use those.  Otherwise, at least check that .firstElementChild ==
+            // .children[0] and .lastElementChild == .children[len - 1], even
+            // if we aren't sure what they should be.
+            expected[node].firstElementChild = expected[node]["children[0]"]
+                ? expected[node]["children[0]"]
+                : eval(node).children[0];
+            expected[node].lastElementChild =
+                expected[node]["children[" + (len - 1) + "]"]
+                ? expected[node]["children[" + (len - 1) + "]"]
+                : eval(node).children[len - 1];
+        }
+        break;
+
+    case Node.TEXT_NODE:
+        expected[node].nodeName = "#text";
+        expected[node]["childNodes.length"] = 0;
+        expected[node].textContent = expected[node].data =
+            expected[node].nodeValue;
+        expected[node].length = expected[node].nodeValue.length;
+        break;
+
+    case Node.PROCESSING_INSTRUCTION_NODE:
+        expected[node].nodeName = expected[node].target;
+        expected[node]["childNodes.length"] = 0;
+        expected[node].textContent = expected[node].data =
+            expected[node].nodeValue;
+        expected[node].length = expected[node].nodeValue.length;
+        break;
+
+    case Node.COMMENT_NODE:
+        expected[node].nodeName = "#comment";
+        expected[node]["childNodes.length"] = 0;
+        expected[node].textContent = expected[node].data =
+            expected[node].nodeValue;
+        expected[node].length = expected[node].nodeValue.length;
+        break;
+
+    case Node.DOCUMENT_NODE:
+        expected[node].nodeName = "#document";
+        expected[node].ownerDocument = expected[node].parentNode =
+            expected[node].parentElement = expected[node].previousSibling =
+            expected[node].nextSibling = expected[node].nodeValue =
+            expected[node].textContent = null;
+        expected[node].documentURI = expected[node].URL;
+        expected[node].charset = expected[node].inputEncoding =
+            expected[node].characterSet;
+        break;
+
+    case Node.DOCUMENT_TYPE_NODE:
+        expected[node].nodeName = expected[node].name;
+        expected[node]["childNodes.length"] = 0;
+        expected[node].parentElement = expected[node].nodeValue =
+            expected[node].textContent = null;
+        break;
+
+    case Node.DOCUMENT_FRAGMENT_NODE:
+        expected[node].nodeName = "#document-fragment";
+        expected[node].parentNode = expected[node].parentElement =
+            expected[node].previousSibling = expected[node].nextSibling =
+            expected[node].nodeValue = null;
+        break;
+    }
+
+    // Now we set some further default values that are independent of node
+    // type.
+    var len = expected[node]["childNodes.length"];
+    if (len === 0) {
+        expected[node].firstChild = expected[node].lastChild = null;
+    } else {
+        // If we have expectations for the first/last child in childNodes, use
+        // those.  Otherwise, at least check that .firstChild == .childNodes[0]
+        // and .lastChild == .childNodes[len - 1], even if we aren't sure what
+        // they should be.
+        expected[node].firstChild = expected[node]["childNodes[0]"]
+            ? expected[node]["childNodes[0]"]
+            : eval(node).childNodes[0];
+        expected[node].lastChild =
+            expected[node]["childNodes[" + (len - 1) + "]"]
+            ? expected[node]["childNodes[" + (len - 1) + "]"]
+            : eval(node).childNodes[len - 1];
+    }
+    expected[node]["hasChildNodes()"] = !!expected[node]["childNodes.length"];
+
+    // Finally, we test!
+    for (var prop in expected[node]) {
+        test(function() {
+            assert_equals(eval(node + "." + prop), expected[node][prop]);
+        }, node + "." + prop);
+    }
+}
+
+testDiv.parentNode.removeChild(testDiv);
+</script>