From 9384772c37751c7f29d4006306479546e749325b Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 19 Nov 2024 15:35:31 +0100 Subject: [PATCH] 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 --- Libraries/LibWeb/DOM/Node.h | 2 +- Libraries/LibWeb/DOMParsing/XMLSerializer.cpp | 14 +- .../wpt-import/dom/nodes/Node-properties.txt | 737 +++++++++++ .../Text/input/wpt-import/dom/common.js | 1089 +++++++++++++++++ .../wpt-import/dom/nodes/Node-properties.html | 710 +++++++++++ 5 files changed, 2545 insertions(+), 7 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/dom/nodes/Node-properties.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/dom/common.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/dom/nodes/Node-properties.html diff --git a/Libraries/LibWeb/DOM/Node.h b/Libraries/LibWeb/DOM/Node.h index a6c8ec9e69f1d..ef4e099a4743d 100644 --- a/Libraries/LibWeb/DOM/Node.h +++ b/Libraries/LibWeb/DOM/Node.h @@ -110,7 +110,7 @@ class Node : public EventTarget { 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; } diff --git a/Libraries/LibWeb/DOMParsing/XMLSerializer.cpp b/Libraries/LibWeb/DOMParsing/XMLSerializer.cpp index 588b41f7c4acc..bf81ec9f1f828 100644 --- a/Libraries/LibWeb/DOMParsing/XMLSerializer.cpp +++ b/Libraries/LibWeb/DOMParsing/XMLSerializer.cpp @@ -196,6 +196,14 @@ WebIDL::ExceptionOr serialize_node_to_xml_string_impl(GC::Ref(*root), require_well_formed); } + // NOTE: CDATASection comes before Text since CDATASection is a subclass of Text. + if (is(*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(*root), require_well_formed); + } + if (is(*root)) { // -> Text // Run the algorithm for XML serializing a Text node node. @@ -220,12 +228,6 @@ WebIDL::ExceptionOr serialize_node_to_xml_string_impl(GC::Ref(*root), require_well_formed); } - if (is(*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(*root), require_well_formed); - } - if (is(*root)) { // -> An Attr object // Return an empty string. diff --git a/Tests/LibWeb/Text/expected/wpt-import/dom/nodes/Node-properties.txt b/Tests/LibWeb/Text/expected/wpt-import/dom/nodes/Node-properties.txt new file mode 100644 index 0000000000000..a1cc1b9b71f9b --- /dev/null +++ b/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() \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/dom/common.js b/Tests/LibWeb/Text/input/wpt-import/dom/common.js new file mode 100644 index 0000000000000..97d7da542bc74 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/dom/common.js @@ -0,0 +1,1089 @@ +"use strict"; +// Written by Aryeh Gregor + +// 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; +} diff --git a/Tests/LibWeb/Text/input/wpt-import/dom/nodes/Node-properties.html b/Tests/LibWeb/Text/input/wpt-import/dom/nodes/Node-properties.html new file mode 100644 index 0000000000000..2f839adeb05e3 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/dom/nodes/Node-properties.html @@ -0,0 +1,710 @@ + +Node assorted property tests + + +
+ + + +