ladybird/Userland/Libraries/LibWeb/DOM/NodeIterator.cpp
Andreas Kling a0b0b29fa1 LibWeb: Minor cleanups in NodeIterator and TreeWalker
- Use TRY() when invoking the NodeFilter
- Say "nullptr" instead of "RefPtr<Node> {}"
2022-03-23 00:19:57 +01:00

207 lines
7.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/DOMExceptionWrapper.h>
#include <LibWeb/Bindings/IDLAbstractOperations.h>
#include <LibWeb/Bindings/NodeWrapper.h>
#include <LibWeb/Bindings/NodeWrapperFactory.h>
#include <LibWeb/DOM/Node.h>
#include <LibWeb/DOM/NodeIterator.h>
namespace Web::DOM {
NodeIterator::NodeIterator(Node& root)
: m_root(root)
, m_reference(root)
{
root.document().register_node_iterator({}, *this);
}
NodeIterator::~NodeIterator()
{
m_root->document().unregister_node_iterator({}, *this);
}
// https://dom.spec.whatwg.org/#dom-document-createnodeiterator
NonnullRefPtr<NodeIterator> NodeIterator::create(Node& root, unsigned what_to_show, RefPtr<NodeFilter> filter)
{
// 1. Let iterator be a new NodeIterator object.
// 2. Set iterators root and iterators reference to root.
auto iterator = adopt_ref(*new NodeIterator(root));
// 3. Set iterators pointer before reference to true.
iterator->m_pointer_before_reference = true;
// 4. Set iterators whatToShow to whatToShow.
iterator->m_what_to_show = what_to_show;
// 5. Set iterators filter to filter.
iterator->m_filter = move(filter);
// 6. Return iterator.
return iterator;
}
// https://dom.spec.whatwg.org/#dom-nodeiterator-detach
void NodeIterator::detach()
{
// The detach() method steps are to do nothing.
// Its functionality (disabling a NodeIterator object) was removed, but the method itself is preserved for compatibility.
}
// https://dom.spec.whatwg.org/#concept-nodeiterator-traverse
JS::ThrowCompletionOr<RefPtr<Node>> NodeIterator::traverse(Direction direction)
{
// 1. Let node be iterators reference.
auto node = m_reference;
// 2. Let beforeNode be iterators pointer before reference.
auto before_node = m_pointer_before_reference;
// 3. While true:
while (true) {
// 4. Branch on direction:
if (direction == Direction::Next) {
// next
// If beforeNode is false, then set node to the first node following node in iterators iterator collection.
// If there is no such node, then return null.
if (!before_node) {
auto* next_node = node->next_in_pre_order(m_root.ptr());
if (!next_node)
return nullptr;
node = *next_node;
} else {
// If beforeNode is true, then set it to false.
before_node = false;
}
} else {
// previous
// If beforeNode is true, then set node to the first node preceding node in iterators iterator collection.
// If there is no such node, then return null.
if (before_node) {
if (node == m_root.ptr())
return nullptr;
auto* previous_node = node->previous_in_pre_order();
if (!previous_node)
return nullptr;
node = *previous_node;
} else {
// If beforeNode is false, then set it to true.
before_node = true;
}
}
// 2. Let result be the result of filtering node within iterator.
auto result = TRY(filter(*node));
// 3. If result is FILTER_ACCEPT, then break.
if (result == NodeFilter::FILTER_ACCEPT)
break;
}
// 4. Set iterators reference to node.
m_reference = node;
// 5. Set iterators pointer before reference to beforeNode.
m_pointer_before_reference = before_node;
// 6. Return node.
return node;
}
// https://dom.spec.whatwg.org/#concept-node-filter
JS::ThrowCompletionOr<NodeFilter::Result> NodeIterator::filter(Node& node)
{
VERIFY(wrapper());
auto& global_object = wrapper()->global_object();
// 1. If traversers active flag is set, then throw an "InvalidStateError" DOMException.
if (m_active)
return JS::throw_completion(wrap(global_object, InvalidStateError::create("NodeIterator is already active")));
// 2. Let n be nodes nodeType attribute value 1.
auto n = node.node_type() - 1;
// 3. If the nth bit (where 0 is the least significant bit) of traversers whatToShow is not set, then return FILTER_SKIP.
if (!(m_what_to_show & (1u << n)))
return NodeFilter::FILTER_SKIP;
// 4. If traversers filter is null, then return FILTER_ACCEPT.
if (!m_filter)
return NodeFilter::FILTER_ACCEPT;
// 5. Set traversers active flag.
m_active = true;
// 6. Let result be the return value of call a user objects operation with traversers filter, "acceptNode", and « node ».
// If this throws an exception, then unset traversers active flag and rethrow the exception.
auto result = Bindings::IDL::call_user_object_operation(m_filter->callback(), "acceptNode", {}, wrap(global_object, node));
if (result.is_abrupt()) {
m_active = false;
return result;
}
// 7. Unset traversers active flag.
m_active = false;
// 8. Return result.
auto result_value = TRY(result.value()->to_i32(global_object));
return static_cast<NodeFilter::Result>(result_value);
}
// https://dom.spec.whatwg.org/#dom-nodeiterator-nextnode
JS::ThrowCompletionOr<RefPtr<Node>> NodeIterator::next_node()
{
return traverse(Direction::Next);
}
// https://dom.spec.whatwg.org/#dom-nodeiterator-previousnode
JS::ThrowCompletionOr<RefPtr<Node>> NodeIterator::previous_node()
{
return traverse(Direction::Previous);
}
// https://dom.spec.whatwg.org/#nodeiterator-pre-removing-steps
void NodeIterator::run_pre_removing_steps(Node& to_be_removed_node)
{
// NOTE: This function implements what the DOM specification tells us to do.
// However, it's known to not match other browsers: https://github.com/whatwg/dom/issues/907
// 1. If toBeRemovedNode is not an inclusive ancestor of nodeIterators reference, or toBeRemovedNode is nodeIterators root, then return.
if (!to_be_removed_node.is_inclusive_ancestor_of(m_reference) || &to_be_removed_node == m_root)
return;
// 2. If nodeIterators pointer before reference is true, then:
if (m_pointer_before_reference) {
// 1. Let next be toBeRemovedNodes first following node that is an inclusive descendant of nodeIterators root and is not an inclusive descendant of toBeRemovedNode, and null if there is no such node.
RefPtr<Node> next = to_be_removed_node.next_in_pre_order(m_root);
while (next && (!next->is_inclusive_descendant_of(m_root) || next->is_inclusive_descendant_of(to_be_removed_node)))
next = next->next_in_pre_order(m_root);
// 2. If next is non-null, then set nodeIterators reference to next and return.
if (next) {
m_reference = *next;
return;
}
// 3. Otherwise, set nodeIterators pointer before reference to false.
m_pointer_before_reference = false;
}
// 3. Set nodeIterators reference to toBeRemovedNodes parent, if toBeRemovedNodes previous sibling is null,
if (!to_be_removed_node.previous_sibling()) {
VERIFY(to_be_removed_node.parent());
m_reference = *to_be_removed_node.parent();
} else {
// ...and to the inclusive descendant of toBeRemovedNodes previous sibling that appears last in tree order otherwise.
auto* node = to_be_removed_node.previous_sibling();
while (node->last_child())
node = node->last_child();
m_reference = *node;
}
}
}