Browse Source

LibWeb: Implement manual slottable assignment

This implements manual slottable assignment by way of HTMLSlotElement's
`assign` API. This includes all of the slottable-related AOs needed to
perform the assignment.
Timothy Flynn 1 year ago
parent
commit
e9da74ebe0

+ 150 - 0
Userland/Libraries/LibWeb/DOM/Slottable.cpp

@@ -29,4 +29,154 @@ JS::GCPtr<HTML::HTMLSlotElement> SlottableMixin::assigned_slot()
     return nullptr;
 }
 
+JS::GCPtr<HTML::HTMLSlotElement> assigned_slot_for_node(JS::NonnullGCPtr<Node> node)
+{
+    if (!node->is_slottable())
+        return nullptr;
+
+    return node->as_slottable().visit([](auto const& slottable) {
+        return slottable->assigned_slot_internal();
+    });
+}
+
+// https://dom.spec.whatwg.org/#slotable-assigned
+bool is_an_assigned_slottable(JS::NonnullGCPtr<Node> node)
+{
+    if (!node->is_slottable())
+        return false;
+
+    // A slottable is assigned if its assigned slot is non-null.
+    return assigned_slot_for_node(node) != nullptr;
+}
+
+// https://dom.spec.whatwg.org/#find-a-slot
+JS::GCPtr<HTML::HTMLSlotElement> find_a_slot(Slottable const& slottable, OpenFlag open_flag)
+{
+    // 1. If slottable’s parent is null, then return null.
+    auto* parent = slottable.visit([](auto& node) { return node->parent_element(); });
+    if (!parent)
+        return nullptr;
+
+    // 2. Let shadow be slottable’s parent’s shadow root.
+    auto* shadow = parent->shadow_root_internal();
+
+    // 3. If shadow is null, then return null.
+    if (shadow == nullptr)
+        return nullptr;
+
+    // 4. If the open flag is set and shadow’s mode is not "open", then return null.
+    if (open_flag == OpenFlag::Set && shadow->mode() != Bindings::ShadowRootMode::Open)
+        return nullptr;
+
+    // 5. If shadow’s slot assignment is "manual", then return the slot in shadow’s descendants whose manually assigned
+    //    nodes contains slottable, if any; otherwise null.
+    if (shadow->slot_assignment() == Bindings::SlotAssignmentMode::Manual) {
+        JS::GCPtr<HTML::HTMLSlotElement> slot;
+
+        shadow->for_each_in_subtree_of_type<HTML::HTMLSlotElement>([&](auto& child) {
+            if (!child.manually_assigned_nodes().contains_slow(slottable))
+                return IterationDecision::Continue;
+
+            slot = child;
+            return IterationDecision::Break;
+        });
+
+        return slot;
+    }
+
+    // FIXME: 6. Return the first slot in tree order in shadow’s descendants whose name is slottable’s name, if any; otherwise null.
+    return nullptr;
+}
+
+// https://dom.spec.whatwg.org/#find-slotables
+Vector<Slottable> find_slottables(JS::NonnullGCPtr<HTML::HTMLSlotElement> slot)
+{
+    // 1. Let result be an empty list.
+    Vector<Slottable> result;
+
+    // 2. Let root be slot’s root.
+    auto& root = slot->root();
+
+    // 3. If root is not a shadow root, then return result.
+    if (!root.is_shadow_root())
+        return result;
+
+    // 4. Let host be root’s host.
+    auto& shadow_root = static_cast<ShadowRoot&>(root);
+    auto* host = shadow_root.host();
+
+    // 5. If root’s slot assignment is "manual", then:
+    if (shadow_root.slot_assignment() == Bindings::SlotAssignmentMode::Manual) {
+        // 1. Let result be « ».
+        // 2. For each slottable slottable of slot’s manually assigned nodes, if slottable’s parent is host, append slottable to result.
+        for (auto const& slottable : slot->manually_assigned_nodes()) {
+            auto const* parent = slottable.visit([](auto const& node) { return node->parent(); });
+
+            if (parent == host)
+                result.append(slottable);
+        }
+    }
+    // 6. Otherwise, for each slottable child slottable of host, in tree order:
+    else {
+        // FIXME: 1. Let foundSlot be the result of finding a slot given slottable.
+        // FIXME: 2. If foundSlot is slot, then append slottable to result.
+    }
+
+    // 7. Return result.
+    return result;
+}
+
+// https://dom.spec.whatwg.org/#assign-slotables
+void assign_slottables(JS::NonnullGCPtr<HTML::HTMLSlotElement> slot)
+{
+    // 1. Let slottables be the result of finding slottables for slot.
+    auto slottables = find_slottables(slot);
+
+    // 2. If slottables and slot’s assigned nodes are not identical, then run signal a slot change for slot.
+    if (slottables != slot->assigned_nodes_internal())
+        signal_a_slot_change(slot);
+
+    // 4. For each slottable in slottables, set slottable’s assigned slot to slot.
+    for (auto& slottable : slottables) {
+        slottable.visit([&](auto& node) {
+            node->set_assigned_slot(slot);
+        });
+    }
+
+    // 3. Set slot’s assigned nodes to slottables.
+    // NOTE: We do this step last so that we can move the slottables list.
+    slot->set_assigned_nodes(move(slottables));
+}
+
+// https://dom.spec.whatwg.org/#assign-slotables-for-a-tree
+void assign_slottables_for_a_tree(JS::NonnullGCPtr<Node> root)
+{
+    // To assign slottables for a tree, given a node root, run assign slottables for each slot slot in root’s inclusive
+    // descendants, in tree order.
+    root->for_each_in_inclusive_subtree_of_type<HTML::HTMLSlotElement>([](auto& slot) {
+        assign_slottables(slot);
+        return IterationDecision::Continue;
+    });
+}
+
+// https://dom.spec.whatwg.org/#assign-a-slot
+void assign_a_slot(Slottable const& slottable)
+{
+    // 1. Let slot be the result of finding a slot with slottable.
+    auto slot = find_a_slot(slottable);
+
+    // 2. If slot is non-null, then run assign slottables for slot.
+    if (slot != nullptr)
+        assign_slottables(*slot);
+}
+
+// https://dom.spec.whatwg.org/#signal-a-slot-change
+void signal_a_slot_change(JS::NonnullGCPtr<HTML::HTMLSlotElement> slottable)
+{
+    // FIXME: 1. Append slot to slot’s relevant agent’s signal slots.
+
+    // 2. Queue a mutation observer microtask.
+    Bindings::queue_mutation_observer_microtask(slottable->document());
+}
+
 }

+ 15 - 0
Userland/Libraries/LibWeb/DOM/Slottable.h

@@ -47,4 +47,19 @@ private:
     JS::GCPtr<HTML::HTMLSlotElement> m_manual_slot_assignment;
 };
 
+enum class OpenFlag {
+    Set,
+    Unset,
+};
+
+JS::GCPtr<HTML::HTMLSlotElement> assigned_slot_for_node(JS::NonnullGCPtr<Node>);
+bool is_an_assigned_slottable(JS::NonnullGCPtr<Node>);
+
+JS::GCPtr<HTML::HTMLSlotElement> find_a_slot(Slottable const&, OpenFlag = OpenFlag::Unset);
+Vector<Slottable> find_slottables(JS::NonnullGCPtr<HTML::HTMLSlotElement>);
+void assign_slottables(JS::NonnullGCPtr<HTML::HTMLSlotElement>);
+void assign_slottables_for_a_tree(JS::NonnullGCPtr<Node>);
+void assign_a_slot(Slottable const&);
+void signal_a_slot_change(JS::NonnullGCPtr<HTML::HTMLSlotElement>);
+
 }

+ 60 - 0
Userland/Libraries/LibWeb/HTML/HTMLSlotElement.cpp

@@ -6,6 +6,8 @@
  */
 
 #include <LibWeb/Bindings/Intrinsics.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/DOM/Text.h>
 #include <LibWeb/HTML/HTMLSlotElement.h>
 
 namespace Web::HTML {
@@ -27,6 +29,64 @@ void HTMLSlotElement::visit_edges(JS::Cell::Visitor& visitor)
 {
     Base::visit_edges(visitor);
     Slot::visit_edges(visitor);
+
+    for (auto const& node : m_manually_assigned_nodes)
+        node.visit([&](auto const& slottable) { visitor.visit(slottable); });
+}
+
+// https://html.spec.whatwg.org/multipage/scripting.html#dom-slot-assignednodes
+Vector<JS::Handle<DOM::Node>> HTMLSlotElement::assigned_nodes(AssignedNodesOptions)
+{
+    // FIXME: 1. If options["flatten"] is false, then return this's assigned nodes.
+    // FIXME: 2. Return the result of finding flattened slottables with this.
+    return {};
+}
+
+// https://html.spec.whatwg.org/multipage/scripting.html#dom-slot-assignedelements
+Vector<JS::Handle<DOM::Element>> HTMLSlotElement::assigned_elements(AssignedNodesOptions)
+{
+    // FIXME: 1. If options["flatten"] is false, then return this's assigned nodes, filtered to contain only Element nodes.
+    // FIXME: 2. Return the result of finding flattened slottables with this, filtered to contain only Element nodes.
+    return {};
+}
+
+// https://html.spec.whatwg.org/multipage/scripting.html#dom-slot-assign
+void HTMLSlotElement::assign(Vector<SlottableHandle> nodes)
+{
+    // 1. For each node of this's manually assigned nodes, set node's manual slot assignment to null.
+    for (auto& node : m_manually_assigned_nodes) {
+        node.visit([&](auto& node) {
+            node->set_manual_slot_assignment(nullptr);
+        });
+    }
+
+    // 2. Let nodesSet be a new ordered set.
+    Vector<DOM::Slottable> nodes_set;
+
+    // 3. For each node of nodes:
+    for (auto& node_handle : nodes) {
+        auto& node = node_handle.visit([](auto& node) -> DOM::SlottableMixin& { return *node; });
+        auto slottable = node_handle.visit([](auto& node) { return node->as_slottable(); });
+
+        // 1. If node's manual slot assignment refers to a slot, then remove node from that slot's manually assigned nodes.
+        if (node.manual_slot_assignment() != nullptr) {
+            m_manually_assigned_nodes.remove_all_matching([&](auto const& manually_assigned_node) {
+                return slottable == manually_assigned_node;
+            });
+        }
+
+        // 2. Set node's manual slot assignment to this.
+        node.set_manual_slot_assignment(this);
+
+        // 3. Append node to nodesSet.
+        nodes_set.append(slottable);
+    }
+
+    // 4. Set this's manually assigned nodes to nodesSet.
+    m_manually_assigned_nodes = move(nodes_set);
+
+    // 5. Run assign slottables for a tree for this's root.
+    assign_slottables_for_a_tree(root());
 }
 
 }

+ 19 - 0
Userland/Libraries/LibWeb/HTML/HTMLSlotElement.h

@@ -7,11 +7,19 @@
 
 #pragma once
 
+#include <AK/Variant.h>
+#include <AK/Vector.h>
+#include <LibJS/Heap/Handle.h>
 #include <LibWeb/DOM/Slot.h>
+#include <LibWeb/DOM/Slottable.h>
 #include <LibWeb/HTML/HTMLElement.h>
 
 namespace Web::HTML {
 
+struct AssignedNodesOptions {
+    bool flatten { false };
+};
+
 class HTMLSlotElement final
     : public HTMLElement
     , public DOM::Slot {
@@ -20,11 +28,22 @@ class HTMLSlotElement final
 public:
     virtual ~HTMLSlotElement() override;
 
+    Vector<JS::Handle<DOM::Node>> assigned_nodes(AssignedNodesOptions options = {});
+    Vector<JS::Handle<DOM::Element>> assigned_elements(AssignedNodesOptions options = {});
+
+    using SlottableHandle = Variant<JS::Handle<DOM::Element>, JS::Handle<DOM::Text>>;
+    void assign(Vector<SlottableHandle> nodes);
+
+    ReadonlySpan<DOM::Slottable> manually_assigned_nodes() const { return m_manually_assigned_nodes; }
+
 private:
     HTMLSlotElement(DOM::Document&, DOM::QualifiedName);
 
     virtual void initialize(JS::Realm&) override;
     virtual void visit_edges(JS::Cell::Visitor&) override;
+
+    // https://html.spec.whatwg.org/multipage/scripting.html#manually-assigned-nodes
+    Vector<DOM::Slottable> m_manually_assigned_nodes;
 };
 
 }

+ 9 - 0
Userland/Libraries/LibWeb/HTML/HTMLSlotElement.idl

@@ -1,3 +1,6 @@
+#import <DOM/Element.idl>
+#import <DOM/Node.idl>
+#import <DOM/Text.idl>
 #import <HTML/HTMLElement.idl>
 
 // https://html.spec.whatwg.org/multipage/scripting.html#htmlslotelement
@@ -7,5 +10,11 @@ interface HTMLSlotElement : HTMLElement {
     [HTMLConstructor] constructor();
 
     [CEReactions, Reflect] attribute DOMString name;
+    sequence<Node> assignedNodes(optional AssignedNodesOptions options = {});
+    sequence<Element> assignedElements(optional AssignedNodesOptions options = {});
+    undefined assign((Element or Text)... nodes);
+};
 
+dictionary AssignedNodesOptions {
+    boolean flatten = false;
 };