1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 16:28:11 +00:00

LibWeb: Implement all "childList" mutation records for MutationObserver

This commit is contained in:
Luke Wilde 2022-07-11 16:39:14 +01:00 committed by Andreas Kling
parent c9ba5531e0
commit 56cfd5ced8
3 changed files with 135 additions and 22 deletions

View file

@ -11,6 +11,7 @@
#include <LibJS/AST.h>
#include <LibJS/Runtime/FunctionObject.h>
#include <LibWeb/Bindings/EventWrapper.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Bindings/NodeWrapper.h>
#include <LibWeb/Bindings/NodeWrapperFactory.h>
#include <LibWeb/DOM/Comment.h>
@ -21,9 +22,11 @@
#include <LibWeb/DOM/EventDispatcher.h>
#include <LibWeb/DOM/IDLEventListener.h>
#include <LibWeb/DOM/LiveNodeList.h>
#include <LibWeb/DOM/MutationType.h>
#include <LibWeb/DOM/Node.h>
#include <LibWeb/DOM/ProcessingInstruction.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/DOM/StaticNodeList.h>
#include <LibWeb/HTML/BrowsingContextContainer.h>
#include <LibWeb/HTML/HTMLAnchorElement.h>
#include <LibWeb/HTML/Parser/HTMLParser.h>
@ -345,7 +348,7 @@ void Node::insert_before(NonnullRefPtr<Node> node, RefPtr<Node> child, bool supp
// 1. Let nodes be nodes children, if node is a DocumentFragment node; otherwise « node ».
NonnullRefPtrVector<Node> nodes;
if (is<DocumentFragment>(*node))
nodes = verify_cast<DocumentFragment>(*node).children_as_vector();
nodes = node->children_as_vector();
else
nodes.append(node);
@ -361,8 +364,9 @@ void Node::insert_before(NonnullRefPtr<Node> node, RefPtr<Node> child, bool supp
// 1. Remove its children with the suppress observers flag set.
node->remove_all_children(true);
// FIXME: 2. Queue a tree mutation record for node with « », nodes, null, and null.
// 2. Queue a tree mutation record for node with « », nodes, null, and null.
// NOTE: This step intentionally does not pay attention to the suppress observers flag.
node->queue_tree_mutation_record(StaticNodeList::create({}), StaticNodeList::create(nodes), nullptr, nullptr);
}
// 5. If child is non-null, then:
@ -380,7 +384,12 @@ void Node::insert_before(NonnullRefPtr<Node> node, RefPtr<Node> child, bool supp
}
}
// FIXME: 6. Let previousSibling be childs previous sibling or parents last child if child is null. (Currently unused so not included)
// 6. Let previousSibling be childs previous sibling or parents last child if child is null.
RefPtr<Node> previous_sibling;
if (child)
previous_sibling = child->previous_sibling();
else
previous_sibling = last_child();
// 7. For each node in nodes, in tree order:
// FIXME: In tree order
@ -418,9 +427,8 @@ void Node::insert_before(NonnullRefPtr<Node> node, RefPtr<Node> child, bool supp
}
// 8. If suppress observers flag is unset, then queue a tree mutation record for parent with nodes, « », previousSibling, and child.
if (!suppress_observers) {
// FIXME: queue a tree mutation record for parent with nodes, « », previousSibling, and child.
}
if (!suppress_observers)
queue_tree_mutation_record(StaticNodeList::create(move(nodes)), StaticNodeList::create({}), previous_sibling, child);
// 9. Run the children changed steps for parent.
children_changed();
@ -517,8 +525,11 @@ void Node::remove(bool suppress_observers)
node_iterator.run_pre_removing_steps(*this);
});
// FIXME: 9. Let oldPreviousSibling be nodes previous sibling. (Currently unused so not included)
// FIXME: 10. Let oldNextSibling be nodes next sibling. (Currently unused so not included)
// 9. Let oldPreviousSibling be nodes previous sibling.
RefPtr<Node> old_previous_sibling = previous_sibling();
// 10. Let oldNextSibling be nodes next sibling.
RefPtr<Node> old_next_sibling = next_sibling();
// 11. Remove node from its parents children.
parent->TreeNode::remove_child(*this);
@ -558,7 +569,9 @@ void Node::remove(bool suppress_observers)
// 20. If suppress observers flag is unset, then queue a tree mutation record for parent with « », « node », oldPreviousSibling, and oldNextSibling.
if (!suppress_observers) {
// FIXME: queue a tree mutation record for parent with « », « node », oldPreviousSibling, and oldNextSibling.
NonnullRefPtrVector<Node> removed_nodes;
removed_nodes.append(*this);
queue_tree_mutation_record(StaticNodeList::create({}), StaticNodeList::create(move(removed_nodes)), old_previous_sibling, old_next_sibling);
}
// 21. Run the children changed steps for parent.
@ -617,30 +630,40 @@ ExceptionOr<NonnullRefPtr<Node>> Node::replace_child(NonnullRefPtr<Node> node, N
}
// 7. Let referenceChild be childs next sibling.
auto reference_child = child->next_sibling();
RefPtr<Node> reference_child = child->next_sibling();
// 8. If referenceChild is node, then set referenceChild to nodes next sibling.
if (reference_child == node)
reference_child = node->next_sibling();
// FIXME: 9. Let previousSibling be childs previous sibling. (Currently unused so not included)
// FIXME: 10. Let removedNodes be the empty set. (Currently unused so not included)
// 9. Let previousSibling be childs previous sibling.
RefPtr<Node> previous_sibling = child->previous_sibling();
// 10. Let removedNodes be the empty set.
NonnullRefPtrVector<Node> removed_nodes;
// 11. If childs parent is non-null, then:
// NOTE: The above can only be false if child is node.
if (child->parent()) {
// FIXME: 1. Set removedNodes to « child ».
// 1. Set removedNodes to « child ».
removed_nodes.append(child);
// 2. Remove child with the suppress observers flag set.
child->remove(true);
}
// FIXME: 12. Let nodes be nodes children if node is a DocumentFragment node; otherwise « node ». (Currently unused so not included)
// 12. Let nodes be nodes children if node is a DocumentFragment node; otherwise « node ».
NonnullRefPtrVector<Node> nodes;
if (is<DocumentFragment>(*node))
nodes = node->children_as_vector();
else
nodes.append(node);
// 13. Insert node into parent before referenceChild with the suppress observers flag set.
insert_before(node, reference_child, true);
// FIXME: 14. Queue a tree mutation record for parent with nodes, removedNodes, previousSibling, and referenceChild.
// 14. Queue a tree mutation record for parent with nodes, removedNodes, previousSibling, and referenceChild.
queue_tree_mutation_record(StaticNodeList::create(move(nodes)), StaticNodeList::create(move(removed_nodes)), previous_sibling, reference_child);
// 15. Return child.
return child;
@ -1085,10 +1108,20 @@ bool Node::is_shadow_including_inclusive_ancestor_of(Node const& other) const
// https://dom.spec.whatwg.org/#concept-node-replace-all
void Node::replace_all(RefPtr<Node> node)
{
// FIXME: 1. Let removedNodes be parents children. (Current unused so not included)
// FIXME: 2. Let addedNodes be the empty set. (Currently unused so not included)
// FIXME: 3. If node is a DocumentFragment node, then set addedNodes to nodes children.
// FIXME: 4. Otherwise, if node is non-null, set addedNodes to « node ».
// 1. Let removedNodes be parents children.
auto removed_nodes = children_as_vector();
// 2. Let addedNodes be the empty set.
NonnullRefPtrVector<Node> added_nodes;
// 3. If node is a DocumentFragment node, then set addedNodes to nodes children.
if (node && is<DocumentFragment>(*node)) {
added_nodes = node->children_as_vector();
}
// 4. Otherwise, if node is non-null, set addedNodes to « node ».
else if (node) {
added_nodes.append(*node);
}
// 5. Remove all parents children, in tree order, with the suppress observers flag set.
remove_all_children(true);
@ -1097,7 +1130,9 @@ void Node::replace_all(RefPtr<Node> node)
if (node)
insert_before(*node, nullptr, true);
// FIXME: 7. If either addedNodes or removedNodes is not empty, then queue a tree mutation record for parent with addedNodes, removedNodes, null, and null.
// 7. If either addedNodes or removedNodes is not empty, then queue a tree mutation record for parent with addedNodes, removedNodes, null, and null.
if (!added_nodes.is_empty() || !removed_nodes.is_empty())
queue_tree_mutation_record(StaticNodeList::create(move(added_nodes)), StaticNodeList::create(move(removed_nodes)), nullptr, nullptr);
}
// https://dom.spec.whatwg.org/#string-replace-all
@ -1282,4 +1317,74 @@ Painting::PaintableBox const* Node::paint_box() const
return static_cast<Layout::Box const&>(*layout_node()).paint_box();
}
// https://dom.spec.whatwg.org/#queue-a-mutation-record
void Node::queue_mutation_record(FlyString const& type, String attribute_name, String attribute_namespace, String old_value, NonnullRefPtr<NodeList> added_nodes, NonnullRefPtr<NodeList> removed_nodes, Node* previous_sibling, Node* next_sibling)
{
// 1. Let interestedObservers be an empty map.
// mutationObserver -> mappedOldValue
OrderedHashMap<NonnullRefPtr<MutationObserver>, String> interested_observers;
// 2. Let nodes be the inclusive ancestors of target.
NonnullRefPtrVector<Node> nodes;
nodes.append(*this);
for (auto* parent_node = parent(); parent_node; parent_node = parent_node->parent())
nodes.append(*parent_node);
// 3. For each node in nodes, and then for each registered of nodes registered observer list:
for (auto& node : nodes) {
for (auto& registered_observer : node.m_registered_observer_list) {
// 1. Let options be registereds options.
auto& options = registered_observer.options;
// 2. If none of the following are true
// - node is not target and options["subtree"] is false
// - type is "attributes" and options["attributes"] either does not exist or is false
// - type is "attributes", options["attributeFilter"] exists, and options["attributeFilter"] does not contain name or namespace is non-null
// - type is "characterData" and options["characterData"] either does not exist or is false
// - type is "childList" and options["childList"] is false
// then:
if (!(&node != this && !options.subtree)
&& !(type == MutationType::attributes && (!options.attributes.has_value() || !options.attributes.value()))
&& !(type == MutationType::attributes && options.attribute_filter.has_value() && (!attribute_namespace.is_null() || !options.attribute_filter->contains_slow(attribute_name)))
&& !(type == MutationType::characterData && (!options.character_data.has_value() || !options.character_data.value()))
&& !(type == MutationType::childList && !options.child_list)) {
// 1. Let mo be registereds observer.
auto mutation_observer = registered_observer.observer;
// 2. If interestedObservers[mo] does not exist, then set interestedObservers[mo] to null.
if (!interested_observers.contains(mutation_observer))
interested_observers.set(mutation_observer, {});
// 3. If either type is "attributes" and options["attributeOldValue"] is true, or type is "characterData" and options["characterDataOldValue"] is true, then set interestedObservers[mo] to oldValue.
if ((type == MutationType::attributes && options.attribute_old_value.has_value() && options.attribute_old_value.value()) || (type == MutationType::characterData && options.character_data_old_value.has_value() && options.character_data_old_value.value()))
interested_observers.set(mutation_observer, old_value);
}
}
}
// 4. For each observer → mappedOldValue of interestedObservers:
for (auto& interested_observer : interested_observers) {
// 1. Let record be a new MutationRecord object with its type set to type, target set to target, attributeName set to name, attributeNamespace set to namespace, oldValue set to mappedOldValue,
// addedNodes set to addedNodes, removedNodes set to removedNodes, previousSibling set to previousSibling, and nextSibling set to nextSibling.
auto record = MutationRecord::create(type, *this, added_nodes, removed_nodes, previous_sibling, next_sibling, attribute_name, attribute_namespace, /* mappedOldValue */ interested_observer.value);
// 2. Enqueue record to observers record queue.
interested_observer.key->enqueue_record({}, move(record));
}
// 5. Queue a mutation observer microtask.
Bindings::queue_mutation_observer_microtask(document());
}
// https://dom.spec.whatwg.org/#queue-a-tree-mutation-record
void Node::queue_tree_mutation_record(NonnullRefPtr<NodeList> added_nodes, NonnullRefPtr<NodeList> removed_nodes, Node* previous_sibling, Node* next_sibling)
{
// 1. Assert: either addedNodes or removedNodes is not empty.
VERIFY(added_nodes->length() > 0 || removed_nodes->length() > 0);
// 2. Queue a mutation record of "childList" for target with null, null, null, addedNodes, removedNodes, previousSibling, and nextSibling.
queue_mutation_record(MutationType::childList, {}, {}, {}, move(added_nodes), move(removed_nodes), previous_sibling, next_sibling);
}
}