Переглянути джерело

LibWeb: Implement all "childList" mutation records for MutationObserver

Luke Wilde 3 роки тому
батько
коміт
56cfd5ced8

+ 5 - 2
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);
+                }
             }
         }
 

+ 125 - 20
Userland/Libraries/LibWeb/DOM/Node.cpp

@@ -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 node’s 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 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<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 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<Node> old_previous_sibling = previous_sibling();
+
+    // 10. Let oldNextSibling be node’s next sibling.
+    RefPtr<Node> 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<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 child’s next sibling.
-    auto reference_child = child->next_sibling();
+    RefPtr<Node> 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<Node> previous_sibling = child->previous_sibling();
+
+    // 10. Let removedNodes be the empty set.
+    NonnullRefPtrVector<Node> 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<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 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<Node> added_nodes;
+
+    // 3. If node is a DocumentFragment node, then set addedNodes to node’s 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 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> 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 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<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);
+}
+
 }

+ 5 - 0
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<NodeList> added_nodes, NonnullRefPtr<NodeList> 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<RegisteredObserver> m_registered_observer_list;
+
+private:
+    void queue_tree_mutation_record(NonnullRefPtr<NodeList> added_nodes, NonnullRefPtr<NodeList> removed_nodes, Node* previous_sibling, Node* next_sibling);
 };
 
 }