1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 02:47:34 +00:00

LibWeb: Implement "NodeIterator pre-removing steps"

These steps run when a node is about to be removed from its parent,
and adjust the position of any live NodeIterators so that they don't
point at a now-removed node.

Note that while this commit implements what's in the DOM specification,
the specification doesn't fully match what other browsers do.

Spec bug: https://github.com/whatwg/dom/issues/907
This commit is contained in:
Andreas Kling 2022-03-09 16:38:44 +01:00
parent acbdb95b0a
commit 9c6999ecf2
5 changed files with 91 additions and 2 deletions

View file

@ -1037,6 +1037,17 @@ void Document::adopt_node(Node& node)
inclusive_descendant.adopted_from(old_document); inclusive_descendant.adopted_from(old_document);
return IterationDecision::Continue; return IterationDecision::Continue;
}); });
// Transfer NodeIterators rooted at `node` from old_document to this document.
Vector<NodeIterator&> node_iterators_to_transfer;
for (auto* node_iterator : old_document.m_node_iterators) {
if (node_iterator->root() == &node)
node_iterators_to_transfer.append(*node_iterator);
}
for (auto& node_iterator : node_iterators_to_transfer) {
old_document.m_node_iterators.remove(&node_iterator);
m_node_iterators.set(&node_iterator);
}
} }
} }
@ -1474,4 +1485,16 @@ NonnullRefPtr<TreeWalker> Document::create_tree_walker(Node& root, unsigned what
return TreeWalker::create(root, what_to_show, move(filter)); return TreeWalker::create(root, what_to_show, move(filter));
} }
void Document::register_node_iterator(Badge<NodeIterator>, NodeIterator& node_iterator)
{
auto result = m_node_iterators.set(&node_iterator);
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
}
void Document::unregister_node_iterator(Badge<NodeIterator>, NodeIterator& node_iterator)
{
bool was_removed = m_node_iterators.remove(&node_iterator);
VERIFY(was_removed);
}
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org> * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -327,6 +327,16 @@ public:
NonnullRefPtr<NodeIterator> create_node_iterator(Node& root, unsigned what_to_show, RefPtr<NodeFilter>); NonnullRefPtr<NodeIterator> create_node_iterator(Node& root, unsigned what_to_show, RefPtr<NodeFilter>);
NonnullRefPtr<TreeWalker> create_tree_walker(Node& root, unsigned what_to_show, RefPtr<NodeFilter>); NonnullRefPtr<TreeWalker> create_tree_walker(Node& root, unsigned what_to_show, RefPtr<NodeFilter>);
void register_node_iterator(Badge<NodeIterator>, NodeIterator&);
void unregister_node_iterator(Badge<NodeIterator>, NodeIterator&);
template<typename Callback>
void for_each_node_iterator(Callback callback)
{
for (auto* node_iterator : m_node_iterators)
callback(*node_iterator);
}
private: private:
explicit Document(const AK::URL&); explicit Document(const AK::URL&);
@ -430,5 +440,8 @@ private:
Vector<WeakPtr<CSS::MediaQueryList>> m_media_query_lists; Vector<WeakPtr<CSS::MediaQueryList>> m_media_query_lists;
bool m_needs_layout { false }; bool m_needs_layout { false };
HashTable<NodeIterator*> m_node_iterators;
}; };
} }

View file

@ -387,7 +387,10 @@ void Node::remove(bool suppress_observers)
// FIXME: For each live range whose start node is parent and start offset is greater than index, decrease its start offset by 1. // FIXME: For each live range whose start node is parent and start offset is greater than index, decrease its start offset by 1.
// FIXME: For each live range whose end node is parent and end offset is greater than index, decrease its end offset by 1. // FIXME: For each live range whose end node is parent and end offset is greater than index, decrease its end offset by 1.
// FIXME: For each NodeIterator object iterator whose roots node document is nodes node document, run the NodeIterator pre-removing steps given node and iterator. // For each NodeIterator object iterator whose roots node document is nodes node document, run the NodeIterator pre-removing steps given node and iterator.
document().for_each_node_iterator([&](NodeIterator& node_iterator) {
node_iterator.run_pre_removing_steps(*this);
});
// FIXME: Let oldPreviousSibling be nodes previous sibling. (Currently unused so not included) // FIXME: Let oldPreviousSibling be nodes previous sibling. (Currently unused so not included)
// FIXME: Let oldNextSibling be nodes next sibling. (Currently unused so not included) // FIXME: Let oldNextSibling be nodes next sibling. (Currently unused so not included)

View file

@ -17,6 +17,12 @@ NodeIterator::NodeIterator(Node& root)
: m_root(root) : m_root(root)
, m_reference(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 // https://dom.spec.whatwg.org/#dom-document-createnodeiterator
@ -160,4 +166,44 @@ JS::ThrowCompletionOr<RefPtr<Node>> NodeIterator::previous_node()
return traverse(Direction::Previous); 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;
}
}
} }

View file

@ -19,6 +19,8 @@ class NodeIterator
public: public:
using WrapperType = Bindings::NodeIteratorWrapper; using WrapperType = Bindings::NodeIteratorWrapper;
virtual ~NodeIterator() override;
static NonnullRefPtr<NodeIterator> create(Node& root, unsigned what_to_show, RefPtr<NodeFilter>); static NonnullRefPtr<NodeIterator> create(Node& root, unsigned what_to_show, RefPtr<NodeFilter>);
NonnullRefPtr<Node> root() { return m_root; } NonnullRefPtr<Node> root() { return m_root; }
@ -33,6 +35,8 @@ public:
void detach(); void detach();
void run_pre_removing_steps(Node&);
private: private:
NodeIterator(Node& root); NodeIterator(Node& root);