From 988c23fff05238dbd1c1899a862bdb4b42b93c73 Mon Sep 17 00:00:00 2001 From: Brian Gianforcaro Date: Sat, 10 Apr 2021 17:21:22 -0700 Subject: [PATCH] LibWeb: Add implementation of Node.compareDocumentPosition() While looking into getting Duck Duck Go loading further in the Browser, I noticed that it was complaining about the missing method Node.compareDocumentPosition. This change implements as much of the DOM spec as possible with the current implementation of the DOM to date. The implementation is validated by new tests in the Node.js. --- Userland/Libraries/LibWeb/DOM/Node.cpp | 37 +++++++++++++++++++++ Userland/Libraries/LibWeb/DOM/Node.h | 1 + Userland/Libraries/LibWeb/DOM/Node.idl | 8 +++++ Userland/Libraries/LibWeb/Tests/DOM/Node.js | 23 +++++++++++++ 4 files changed, 69 insertions(+) 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); + }); });