From 56cfd5ced85f5a8332a9788ed6b0306742abc48b Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Mon, 11 Jul 2022 16:39:14 +0100 Subject: [PATCH] LibWeb: Implement all "childList" mutation records for MutationObserver --- Userland/Libraries/LibWeb/DOM/Document.cpp | 7 +- Userland/Libraries/LibWeb/DOM/Node.cpp | 145 ++++++++++++++++++--- Userland/Libraries/LibWeb/DOM/Node.h | 5 + 3 files changed, 135 insertions(+), 22 deletions(-) diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index cd726b39c7..f072283a75 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -132,8 +132,11 @@ void Document::removed_last_ref() for (auto& node : descendants) { VERIFY(&node.document() == this); VERIFY(!node.is_document()); - if (node.parent()) - node.remove(); + if (node.parent()) { + // We need to suppress mutation observers so that they don't try and queue a microtask for this Document which is in the process of dying, + // which will cause an `!m_in_removed_last_ref` assertion failure when it tries to ref this Document. + node.remove(true); + } } } diff --git a/Userland/Libraries/LibWeb/DOM/Node.cpp b/Userland/Libraries/LibWeb/DOM/Node.cpp index 7e68facfd5..bc1d78258b 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.cpp +++ b/Userland/Libraries/LibWeb/DOM/Node.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -21,9 +22,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -345,7 +348,7 @@ void Node::insert_before(NonnullRefPtr node, RefPtr child, bool supp // 1. Let nodes be node’s children, if node is a DocumentFragment node; otherwise « node ». NonnullRefPtrVector nodes; if (is(*node)) - nodes = verify_cast(*node).children_as_vector(); + nodes = node->children_as_vector(); else nodes.append(node); @@ -361,8 +364,9 @@ void Node::insert_before(NonnullRefPtr node, RefPtr 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, RefPtr child, bool supp } } - // FIXME: 6. Let previousSibling be child’s previous sibling or parent’s last child if child is null. (Currently unused so not included) + // 6. Let previousSibling be child’s previous sibling or parent’s last child if child is null. + RefPtr 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, RefPtr 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 node’s previous sibling. (Currently unused so not included) - // FIXME: 10. Let oldNextSibling be node’s next sibling. (Currently unused so not included) + // 9. Let oldPreviousSibling be node’s previous sibling. + RefPtr old_previous_sibling = previous_sibling(); + + // 10. Let oldNextSibling be node’s next sibling. + RefPtr old_next_sibling = next_sibling(); // 11. Remove node from its parent’s 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 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> Node::replace_child(NonnullRefPtr node, N } // 7. Let referenceChild be child’s next sibling. - auto reference_child = child->next_sibling(); + RefPtr reference_child = child->next_sibling(); // 8. If referenceChild is node, then set referenceChild to node’s next sibling. if (reference_child == node) reference_child = node->next_sibling(); - // FIXME: 9. Let previousSibling be child’s previous sibling. (Currently unused so not included) - // FIXME: 10. Let removedNodes be the empty set. (Currently unused so not included) + // 9. Let previousSibling be child’s previous sibling. + RefPtr previous_sibling = child->previous_sibling(); + + // 10. Let removedNodes be the empty set. + NonnullRefPtrVector removed_nodes; // 11. If child’s 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 node’s children if node is a DocumentFragment node; otherwise « node ». (Currently unused so not included) + // 12. Let nodes be node’s children if node is a DocumentFragment node; otherwise « node ». + NonnullRefPtrVector nodes; + if (is(*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) { - // FIXME: 1. Let removedNodes be parent’s 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 node’s children. - // FIXME: 4. Otherwise, if node is non-null, set addedNodes to « node ». + // 1. Let removedNodes be parent’s children. + auto removed_nodes = children_as_vector(); + + // 2. Let addedNodes be the empty set. + NonnullRefPtrVector added_nodes; + + // 3. If node is a DocumentFragment node, then set addedNodes to node’s children. + if (node && is(*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 parent’s children, in tree order, with the suppress observers flag set. remove_all_children(true); @@ -1097,7 +1130,9 @@ void Node::replace_all(RefPtr 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_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 added_nodes, NonnullRefPtr removed_nodes, Node* previous_sibling, Node* next_sibling) +{ + // 1. Let interestedObservers be an empty map. + // mutationObserver -> mappedOldValue + OrderedHashMap, String> interested_observers; + + // 2. Let nodes be the inclusive ancestors of target. + NonnullRefPtrVector 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 node’s registered observer list: + for (auto& node : nodes) { + for (auto& registered_observer : node.m_registered_observer_list) { + // 1. Let options be registered’s 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 registered’s 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 observer’s 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 added_nodes, NonnullRefPtr 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); +} + } diff --git a/Userland/Libraries/LibWeb/DOM/Node.h b/Userland/Libraries/LibWeb/DOM/Node.h index 293e666b29..d30275ef5c 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.h +++ b/Userland/Libraries/LibWeb/DOM/Node.h @@ -229,6 +229,8 @@ public: void add_registered_observer(RegisteredObserver& registered_observer) { m_registered_observer_list.append(registered_observer); } + void queue_mutation_record(FlyString const& type, String attribute_name, String attribute_namespace, String old_value, NonnullRefPtr added_nodes, NonnullRefPtr removed_nodes, Node* previous_sibling, Node* next_sibling); + protected: Node(Document&, NodeType); @@ -243,6 +245,9 @@ protected: // https://dom.spec.whatwg.org/#registered-observer-list // "Nodes have a strong reference to registered observers in their registered observer list." https://dom.spec.whatwg.org/#garbage-collection NonnullRefPtrVector m_registered_observer_list; + +private: + void queue_tree_mutation_record(NonnullRefPtr added_nodes, NonnullRefPtr removed_nodes, Node* previous_sibling, Node* next_sibling); }; }