From e9da74ebe0ae782b20f1016bd6851cbb0dbf57bc Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 5 Sep 2023 15:05:46 -0400 Subject: [PATCH] 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. --- Userland/Libraries/LibWeb/DOM/Slottable.cpp | 150 ++++++++++++++++++ Userland/Libraries/LibWeb/DOM/Slottable.h | 15 ++ .../Libraries/LibWeb/HTML/HTMLSlotElement.cpp | 60 +++++++ .../Libraries/LibWeb/HTML/HTMLSlotElement.h | 19 +++ .../Libraries/LibWeb/HTML/HTMLSlotElement.idl | 11 +- 5 files changed, 254 insertions(+), 1 deletion(-) diff --git a/Userland/Libraries/LibWeb/DOM/Slottable.cpp b/Userland/Libraries/LibWeb/DOM/Slottable.cpp index 50a97dd6f9..4429db0e40 100644 --- a/Userland/Libraries/LibWeb/DOM/Slottable.cpp +++ b/Userland/Libraries/LibWeb/DOM/Slottable.cpp @@ -29,4 +29,154 @@ JS::GCPtr SlottableMixin::assigned_slot() return nullptr; } +JS::GCPtr assigned_slot_for_node(JS::NonnullGCPtr 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) +{ + 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 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 slot; + + shadow->for_each_in_subtree_of_type([&](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 find_slottables(JS::NonnullGCPtr slot) +{ + // 1. Let result be an empty list. + Vector 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(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 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 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([](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 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()); +} + } diff --git a/Userland/Libraries/LibWeb/DOM/Slottable.h b/Userland/Libraries/LibWeb/DOM/Slottable.h index f9a877b4f7..81de3e29c1 100644 --- a/Userland/Libraries/LibWeb/DOM/Slottable.h +++ b/Userland/Libraries/LibWeb/DOM/Slottable.h @@ -47,4 +47,19 @@ private: JS::GCPtr m_manual_slot_assignment; }; +enum class OpenFlag { + Set, + Unset, +}; + +JS::GCPtr assigned_slot_for_node(JS::NonnullGCPtr); +bool is_an_assigned_slottable(JS::NonnullGCPtr); + +JS::GCPtr find_a_slot(Slottable const&, OpenFlag = OpenFlag::Unset); +Vector find_slottables(JS::NonnullGCPtr); +void assign_slottables(JS::NonnullGCPtr); +void assign_slottables_for_a_tree(JS::NonnullGCPtr); +void assign_a_slot(Slottable const&); +void signal_a_slot_change(JS::NonnullGCPtr); + } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.cpp index 8891332c97..7a6bc694ba 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.cpp @@ -6,6 +6,8 @@ */ #include +#include +#include #include 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> 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> 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 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 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()); } } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.h b/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.h index f0c0d22cc6..cef72057ff 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.h @@ -7,11 +7,19 @@ #pragma once +#include +#include +#include #include +#include #include 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> assigned_nodes(AssignedNodesOptions options = {}); + Vector> assigned_elements(AssignedNodesOptions options = {}); + + using SlottableHandle = Variant, JS::Handle>; + void assign(Vector nodes); + + ReadonlySpan 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 m_manually_assigned_nodes; }; } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.idl index 64cb999bd8..719531d5f3 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.idl +++ b/Userland/Libraries/LibWeb/HTML/HTMLSlotElement.idl @@ -1,3 +1,6 @@ +#import +#import +#import #import // https://html.spec.whatwg.org/multipage/scripting.html#htmlslotelement @@ -7,5 +10,11 @@ interface HTMLSlotElement : HTMLElement { [HTMLConstructor] constructor(); [CEReactions, Reflect] attribute DOMString name; - + sequence assignedNodes(optional AssignedNodesOptions options = {}); + sequence assignedElements(optional AssignedNodesOptions options = {}); + undefined assign((Element or Text)... nodes); +}; + +dictionary AssignedNodesOptions { + boolean flatten = false; };