diff --git a/Userland/Libraries/LibWeb/DOM/Node.cpp b/Userland/Libraries/LibWeb/DOM/Node.cpp index c2792df94a..e4e5429486 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.cpp +++ b/Userland/Libraries/LibWeb/DOM/Node.cpp @@ -454,6 +454,43 @@ void Node::remove_all_children(bool suppress_observers) child->remove(suppress_observers); } +// https://dom.spec.whatwg.org/#dom-node-comparedocumentposition +u16 Node::compare_document_position(RefPtr other) +{ + enum Position : u16 { + DOCUMENT_POSITION_EQUAL = 0, + DOCUMENT_POSITION_DISCONNECTED = 1, + DOCUMENT_POSITION_PRECEDING = 2, + DOCUMENT_POSITION_FOLLOWING = 4, + DOCUMENT_POSITION_CONTAINS = 8, + DOCUMENT_POSITION_CONTAINED_BY = 16, + DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 32, + }; + + if (this == other) + return DOCUMENT_POSITION_EQUAL; + + Node* node1 = other.ptr(); + Node* node2 = this; + + // FIXME: Once LibWeb supports attribute nodes fix to follow the specification. + VERIFY(node1->type() != NodeType::ATTRIBUTE_NODE && node2->type() != NodeType::ATTRIBUTE_NODE); + + if ((node1 == nullptr || node2 == nullptr) || (node1->root() != node2->root())) + return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | (node1 > node2 ? DOCUMENT_POSITION_PRECEDING : DOCUMENT_POSITION_FOLLOWING); + + if (node1->is_ancestor_of(*node2)) + return DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING; + + if (node2->is_ancestor_of(*node1)) + return DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING; + + if (node1->is_before(*node2)) + return DOCUMENT_POSITION_PRECEDING; + else + return DOCUMENT_POSITION_FOLLOWING; +} + // https://dom.spec.whatwg.org/#concept-tree-host-including-inclusive-ancestor bool Node::is_host_including_inclusive_ancestor_of(const Node& other) const { diff --git a/Userland/Libraries/LibWeb/DOM/Node.h b/Userland/Libraries/LibWeb/DOM/Node.h index 946279ae45..0cb36d0113 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.h +++ b/Userland/Libraries/LibWeb/DOM/Node.h @@ -100,6 +100,7 @@ public: void insert_before(NonnullRefPtr node, RefPtr child, bool suppress_observers = false); void remove(bool suppress_observers = false); void remove_all_children(bool suppress_observers = false); + u16 compare_document_position(RefPtr other); // NOTE: This is intended for the JS bindings. bool has_child_nodes() const { return has_children(); } diff --git a/Userland/Libraries/LibWeb/DOM/Node.idl b/Userland/Libraries/LibWeb/DOM/Node.idl index cf98316266..34de218746 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.idl +++ b/Userland/Libraries/LibWeb/DOM/Node.idl @@ -32,4 +32,12 @@ interface Node : EventTarget { const unsigned short DOCUMENT_FRAGMENT_NODE = 11; const unsigned short NOTATION_NODE = 12; + unsigned short compareDocumentPosition(Node? otherNode); + // Node.compareDocumentPosition() constants + const unsigned short DOCUMENT_POSITION_DISCONNECTED = 1; + const unsigned short DOCUMENT_POSITION_PRECEDING = 2; + const unsigned short DOCUMENT_POSITION_FOLLOWING = 4; + const unsigned short DOCUMENT_POSITION_CONTAINS = 8; + const unsigned short DOCUMENT_POSITION_CONTAINED_BY = 16; + const unsigned short DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 32; }; diff --git a/Userland/Libraries/LibWeb/Tests/DOM/Node.js b/Userland/Libraries/LibWeb/Tests/DOM/Node.js index 47c3e1c668..38618c0dd9 100644 --- a/Userland/Libraries/LibWeb/Tests/DOM/Node.js +++ b/Userland/Libraries/LibWeb/Tests/DOM/Node.js @@ -36,4 +36,27 @@ afterInitialPageLoad(() => { document.body.removeChild(element); expect(element.isConnected).toBeFalse(); }); + + test("Node.compareDocumentPosition()", () => { + const head = document.head; + const body = document.body; + + expect(head.compareDocumentPosition(head)).toBe(0); + + // FIXME: Can be uncommented once the IDL parser correctly implements nullable paramaters. + // expect(head.compareDocumentPosition(null) & Node.DOCUMENT_POSITION_DISCONNECTED | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC). + // toBe(Node.DOCUMENT_POSITION_DISCONNECTED | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC); + + expect(head.compareDocumentPosition(body)).toBe(Node.DOCUMENT_POSITION_FOLLOWING); + expect(body.compareDocumentPosition(head)).toBe(Node.DOCUMENT_POSITION_PRECEDING); + + const source = document.getElementById("source"); + expect(source.compareDocumentPosition(body)).toBe( + Node.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_PRECEDING + ); + expect(body.compareDocumentPosition(source)).toBe( + Node.DOCUMENT_POSITION_CONTAINED_BY | Node.DOCUMENT_POSITION_FOLLOWING + ); + expect(source.compareDocumentPosition(head)).toBe(Node.DOCUMENT_POSITION_PRECEDING); + }); });