Serenity Operating System
at master 251 lines 9.2 kB view raw
1/* 2 * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <LibWeb/DOM/Document.h> 8#include <LibWeb/DOM/Node.h> 9#include <LibWeb/DOM/NodeIterator.h> 10#include <LibWeb/WebIDL/AbstractOperations.h> 11 12namespace Web::DOM { 13 14NodeIterator::NodeIterator(Node& root) 15 : PlatformObject(root.realm()) 16 , m_root(root) 17 , m_reference({ root }) 18{ 19 root.document().register_node_iterator({}, *this); 20} 21 22NodeIterator::~NodeIterator() = default; 23 24JS::ThrowCompletionOr<void> NodeIterator::initialize(JS::Realm& realm) 25{ 26 MUST_OR_THROW_OOM(Base::initialize(realm)); 27 set_prototype(&Bindings::ensure_web_prototype<Bindings::NodeIteratorPrototype>(realm, "NodeIterator")); 28 29 return {}; 30} 31 32void NodeIterator::finalize() 33{ 34 Base::finalize(); 35 m_root->document().unregister_node_iterator({}, *this); 36} 37 38void NodeIterator::visit_edges(Cell::Visitor& visitor) 39{ 40 Base::visit_edges(visitor); 41 visitor.visit(m_filter.ptr()); 42 visitor.visit(m_root.ptr()); 43 visitor.visit(m_reference.node.ptr()); 44 45 if (m_traversal_pointer.has_value()) 46 visitor.visit(m_traversal_pointer->node.ptr()); 47} 48 49// https://dom.spec.whatwg.org/#dom-document-createnodeiterator 50WebIDL::ExceptionOr<JS::NonnullGCPtr<NodeIterator>> NodeIterator::create(Node& root, unsigned what_to_show, JS::GCPtr<NodeFilter> filter) 51{ 52 // 1. Let iterator be a new NodeIterator object. 53 // 2. Set iterator’s root and iterator’s reference to root. 54 // 3. Set iterator’s pointer before reference to true. 55 auto& realm = root.realm(); 56 auto iterator = MUST_OR_THROW_OOM(realm.heap().allocate<NodeIterator>(realm, root)); 57 58 // 4. Set iterator’s whatToShow to whatToShow. 59 iterator->m_what_to_show = what_to_show; 60 61 // 5. Set iterator’s filter to filter. 62 iterator->m_filter = filter; 63 64 // 6. Return iterator. 65 return iterator; 66} 67 68// https://dom.spec.whatwg.org/#dom-nodeiterator-detach 69void NodeIterator::detach() 70{ 71 // The detach() method steps are to do nothing. 72 // Its functionality (disabling a NodeIterator object) was removed, but the method itself is preserved for compatibility. 73} 74 75// https://dom.spec.whatwg.org/#concept-nodeiterator-traverse 76JS::ThrowCompletionOr<JS::GCPtr<Node>> NodeIterator::traverse(Direction direction) 77{ 78 // 1. Let node be iterator’s reference. 79 // 2. Let beforeNode be iterator’s pointer before reference. 80 m_traversal_pointer = m_reference; 81 82 JS::GCPtr<Node> candidate; 83 84 // 3. While true: 85 while (true) { 86 // 4. Branch on direction: 87 if (direction == Direction::Next) { 88 // next 89 // If beforeNode is false, then set node to the first node following node in iterator’s iterator collection. 90 // If there is no such node, then return null. 91 if (!m_traversal_pointer->is_before_node) { 92 auto* next_node = m_traversal_pointer->node->next_in_pre_order(m_root.ptr()); 93 if (!next_node) 94 return nullptr; 95 m_traversal_pointer->node = *next_node; 96 } else { 97 // If beforeNode is true, then set it to false. 98 m_traversal_pointer->is_before_node = false; 99 } 100 } else { 101 // previous 102 // If beforeNode is true, then set node to the first node preceding node in iterator’s iterator collection. 103 // If there is no such node, then return null. 104 if (m_traversal_pointer->is_before_node) { 105 if (m_traversal_pointer->node.ptr() == m_root.ptr()) 106 return nullptr; 107 auto* previous_node = m_traversal_pointer->node->previous_in_pre_order(); 108 if (!previous_node) 109 return nullptr; 110 m_traversal_pointer->node = *previous_node; 111 } else { 112 // If beforeNode is false, then set it to true. 113 m_traversal_pointer->is_before_node = true; 114 } 115 } 116 117 // NOTE: If the NodeFilter deletes the iterator's current traversal pointer, 118 // we will automatically retarget it. However, in that case, we're expected 119 // to return the node passed to the filter, not the adjusted traversal pointer's 120 // node after the filter returns! 121 candidate = m_traversal_pointer->node; 122 123 // 2. Let result be the result of filtering node within iterator. 124 auto result = TRY(filter(*m_traversal_pointer->node)); 125 126 // 3. If result is FILTER_ACCEPT, then break. 127 if (result == NodeFilter::Result::FILTER_ACCEPT) 128 break; 129 } 130 131 // 4. Set iterator’s reference to node. 132 // 5. Set iterator’s pointer before reference to beforeNode. 133 m_reference = m_traversal_pointer.release_value(); 134 135 // 6. Return node. 136 return candidate; 137} 138 139// https://dom.spec.whatwg.org/#concept-node-filter 140JS::ThrowCompletionOr<NodeFilter::Result> NodeIterator::filter(Node& node) 141{ 142 // 1. If traverser’s active flag is set, then throw an "InvalidStateError" DOMException. 143 if (m_active) 144 return throw_completion(WebIDL::InvalidStateError::create(realm(), "NodeIterator is already active")); 145 146 // 2. Let n be node’s nodeType attribute value − 1. 147 auto n = node.node_type() - 1; 148 149 // 3. If the nth bit (where 0 is the least significant bit) of traverser’s whatToShow is not set, then return FILTER_SKIP. 150 if (!(m_what_to_show & (1u << n))) 151 return NodeFilter::Result::FILTER_SKIP; 152 153 // 4. If traverser’s filter is null, then return FILTER_ACCEPT. 154 if (!m_filter) 155 return NodeFilter::Result::FILTER_ACCEPT; 156 157 // 5. Set traverser’s active flag. 158 m_active = true; 159 160 // 6. Let result be the return value of call a user object’s operation with traverser’s filter, "acceptNode", and « node ». 161 // If this throws an exception, then unset traverser’s active flag and rethrow the exception. 162 auto result = WebIDL::call_user_object_operation(m_filter->callback(), "acceptNode", {}, &node); 163 if (result.is_abrupt()) { 164 m_active = false; 165 return result; 166 } 167 168 // 7. Unset traverser’s active flag. 169 m_active = false; 170 171 // 8. Return result. 172 auto result_value = TRY(result.value()->to_i32(vm())); 173 return static_cast<NodeFilter::Result>(result_value); 174} 175 176// https://dom.spec.whatwg.org/#dom-nodeiterator-nextnode 177JS::ThrowCompletionOr<JS::GCPtr<Node>> NodeIterator::next_node() 178{ 179 return traverse(Direction::Next); 180} 181 182// https://dom.spec.whatwg.org/#dom-nodeiterator-previousnode 183JS::ThrowCompletionOr<JS::GCPtr<Node>> NodeIterator::previous_node() 184{ 185 return traverse(Direction::Previous); 186} 187 188void NodeIterator::run_pre_removing_steps_with_node_pointer(Node& to_be_removed_node, NodePointer& pointer) 189{ 190 // NOTE: This function tries to match the behavior of other engines, but not the DOM specification 191 // as it's a known issue that the spec doesn't match how major browsers behave. 192 // Spec bug: https://github.com/whatwg/dom/issues/907 193 194 if (!to_be_removed_node.is_descendant_of(root())) 195 return; 196 197 if (!to_be_removed_node.is_inclusive_ancestor_of(pointer.node)) 198 return; 199 200 if (pointer.is_before_node) { 201 if (auto* node = to_be_removed_node.next_in_pre_order(root())) { 202 while (node && node->is_descendant_of(to_be_removed_node)) 203 node = node->next_in_pre_order(root()); 204 if (node) 205 pointer.node = *node; 206 return; 207 } 208 if (auto* node = to_be_removed_node.previous_in_pre_order()) { 209 if (to_be_removed_node.is_ancestor_of(pointer.node)) { 210 while (node && node->is_descendant_of(to_be_removed_node)) 211 node = node->previous_in_pre_order(); 212 } 213 if (node) { 214 pointer = { 215 .node = *node, 216 .is_before_node = false, 217 }; 218 } 219 } 220 return; 221 } 222 223 if (auto* node = to_be_removed_node.previous_in_pre_order()) { 224 if (to_be_removed_node.is_ancestor_of(pointer.node)) { 225 while (node && node->is_descendant_of(to_be_removed_node)) 226 node = node->previous_in_pre_order(); 227 } 228 if (node) 229 pointer.node = *node; 230 return; 231 } 232 auto* node = to_be_removed_node.next_in_pre_order(root()); 233 if (to_be_removed_node.is_ancestor_of(pointer.node)) { 234 while (node && node->is_descendant_of(to_be_removed_node)) 235 node = node->previous_in_pre_order(); 236 } 237 if (node) 238 pointer.node = *node; 239} 240 241// https://dom.spec.whatwg.org/#nodeiterator-pre-removing-steps 242void NodeIterator::run_pre_removing_steps(Node& to_be_removed_node) 243{ 244 // NOTE: If we're in the middle of traversal, we have to adjust the traversal pointer in response to node removal. 245 if (m_traversal_pointer.has_value()) 246 run_pre_removing_steps_with_node_pointer(to_be_removed_node, *m_traversal_pointer); 247 248 run_pre_removing_steps_with_node_pointer(to_be_removed_node, m_reference); 249} 250 251}