mirror of
				https://github.com/LadybirdBrowser/ladybird.git
				synced 2025-10-25 17:39:27 +00:00 
			
		
		
		
	
		
			Some checks are pending
		
		
	
	CI / macOS, arm64, Sanitizer, Clang (push) Waiting to run
				
			CI / Linux, x86_64, Fuzzers, Clang (push) Waiting to run
				
			CI / Linux, x86_64, Sanitizer, GNU (push) Waiting to run
				
			CI / Linux, x86_64, Sanitizer, Clang (push) Waiting to run
				
			Package the js repl as a binary artifact / Linux, arm64 (push) Waiting to run
				
			Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
				
			Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
				
			Run test262 and test-wasm / run_and_update_results (push) Waiting to run
				
			Lint Code / lint (push) Waiting to run
				
			Label PRs with merge conflicts / auto-labeler (push) Waiting to run
				
			Push notes / build (push) Waiting to run
				
			
		
			
				
	
	
		
			265 lines
		
	
	
	
		
			9.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			265 lines
		
	
	
	
		
			9.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | ||
|  * Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
 | ||
|  * Copyright (c) 2025, Shannon Booth <shannon@serenityos.org>
 | ||
|  *
 | ||
|  * SPDX-License-Identifier: BSD-2-Clause
 | ||
|  */
 | ||
| 
 | ||
| #include <LibWeb/Bindings/MainThreadVM.h>
 | ||
| #include <LibWeb/DOM/Element.h>
 | ||
| #include <LibWeb/DOM/Node.h>
 | ||
| #include <LibWeb/DOM/ShadowRoot.h>
 | ||
| #include <LibWeb/DOM/Slottable.h>
 | ||
| #include <LibWeb/DOM/Text.h>
 | ||
| #include <LibWeb/HTML/HTMLSlotElement.h>
 | ||
| #include <LibWeb/HTML/Scripting/SimilarOriginWindowAgent.h>
 | ||
| 
 | ||
| namespace Web::DOM {
 | ||
| 
 | ||
| SlottableMixin::~SlottableMixin() = default;
 | ||
| 
 | ||
| void SlottableMixin::visit_edges(JS::Cell::Visitor& visitor)
 | ||
| {
 | ||
|     visitor.visit(m_assigned_slot);
 | ||
|     visitor.visit(m_manual_slot_assignment);
 | ||
| }
 | ||
| 
 | ||
| // https://dom.spec.whatwg.org/#dom-slotable-assignedslot
 | ||
| GC::Ptr<HTML::HTMLSlotElement> SlottableMixin::assigned_slot()
 | ||
| {
 | ||
|     auto& node = as<DOM::Node>(*this);
 | ||
| 
 | ||
|     // The assignedSlot getter steps are to return the result of find a slot given this and with the open flag set.
 | ||
|     return find_a_slot(node.as_slottable(), OpenFlag::Set);
 | ||
| }
 | ||
| 
 | ||
| GC::Ptr<HTML::HTMLSlotElement> assigned_slot_for_node(GC::Ref<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(GC::Ref<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
 | ||
| GC::Ptr<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();
 | ||
| 
 | ||
|     // 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) {
 | ||
|         GC::Ptr<HTML::HTMLSlotElement> slot;
 | ||
| 
 | ||
|         shadow->for_each_in_subtree_of_type<HTML::HTMLSlotElement>([&](auto& child) {
 | ||
|             if (!child.manually_assigned_nodes().contains_slow(slottable))
 | ||
|                 return TraversalDecision::Continue;
 | ||
| 
 | ||
|             slot = child;
 | ||
|             return TraversalDecision::Break;
 | ||
|         });
 | ||
| 
 | ||
|         return slot;
 | ||
|     }
 | ||
| 
 | ||
|     // 6. Return the first slot in tree order in shadow’s descendants whose name is slottable’s name, if any; otherwise null.
 | ||
|     auto const& slottable_name = slottable.visit([](auto const& node) { return node->slottable_name(); });
 | ||
|     GC::Ptr<HTML::HTMLSlotElement> slot;
 | ||
| 
 | ||
|     shadow->for_each_in_subtree_of_type<HTML::HTMLSlotElement>([&](auto& child) {
 | ||
|         if (child.slot_name() != slottable_name)
 | ||
|             return TraversalDecision::Continue;
 | ||
| 
 | ||
|         slot = child;
 | ||
|         return TraversalDecision::Break;
 | ||
|     });
 | ||
| 
 | ||
|     return slot;
 | ||
| }
 | ||
| 
 | ||
| // https://dom.spec.whatwg.org/#find-slotables
 | ||
| Vector<Slottable> find_slottables(GC::Ref<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 {
 | ||
|         host->for_each_child([&](auto& node) {
 | ||
|             if (!node.is_slottable())
 | ||
|                 return IterationDecision::Continue;
 | ||
| 
 | ||
|             auto slottable = node.as_slottable();
 | ||
| 
 | ||
|             // 1. Let foundSlot be the result of finding a slot given slottable.
 | ||
|             auto found_slot = find_a_slot(slottable);
 | ||
| 
 | ||
|             // 2. If foundSlot is slot, then append slottable to result.
 | ||
|             if (found_slot == slot)
 | ||
|                 result.append(move(slottable));
 | ||
| 
 | ||
|             return IterationDecision::Continue;
 | ||
|         });
 | ||
|     }
 | ||
| 
 | ||
|     // 7. Return result.
 | ||
|     return result;
 | ||
| }
 | ||
| 
 | ||
| // https://dom.spec.whatwg.org/#find-flattened-slotables
 | ||
| Vector<Slottable> find_flattened_slottables(GC::Ref<HTML::HTMLSlotElement> slot)
 | ||
| {
 | ||
|     // 1. Let result be « ».
 | ||
|     Vector<Slottable> result;
 | ||
| 
 | ||
|     // 2. If slot’s root is not a shadow root, then return result.
 | ||
|     if (!slot->root().is_shadow_root())
 | ||
|         return result;
 | ||
| 
 | ||
|     // 3. Let slottables be the result of finding slottables given slot.
 | ||
|     auto slottables = find_slottables(slot);
 | ||
| 
 | ||
|     // 4. If slottables is the empty list, then append each slottable child of slot, in tree order, to slottables.
 | ||
|     if (slottables.is_empty()) {
 | ||
|         slot->for_each_child([&](auto& node) {
 | ||
|             if (!node.is_slottable())
 | ||
|                 return IterationDecision::Continue;
 | ||
| 
 | ||
|             slottables.append(node.as_slottable());
 | ||
|             return IterationDecision::Continue;
 | ||
|         });
 | ||
|     }
 | ||
| 
 | ||
|     // 5. For each node of slottables:
 | ||
|     for (auto& node : slottables) {
 | ||
|         // 1. If node is a slot whose root is a shadow root:
 | ||
|         //     1. Let temporaryResult be the result of finding flattened slottables given node.
 | ||
|         //     2. Append each slottable in temporaryResult, in order, to result.
 | ||
|         // 2. Otherwise, append node to result.
 | ||
|         auto* maybe_slot = node.get_pointer<GC::Ref<DOM::Element>>();
 | ||
|         if (!maybe_slot) {
 | ||
|             result.append(node);
 | ||
|             continue;
 | ||
|         }
 | ||
| 
 | ||
|         if (auto* slot = as_if<HTML::HTMLSlotElement>(maybe_slot->ptr()); slot && slot->root().is_shadow_root()) {
 | ||
|             auto temporary_result = find_flattened_slottables(*slot);
 | ||
|             result.extend(temporary_result);
 | ||
|         } else {
 | ||
|             result.append(node);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     // 6. Return result.
 | ||
|     return result;
 | ||
| }
 | ||
| 
 | ||
| // https://dom.spec.whatwg.org/#assign-slotables
 | ||
| void assign_slottables(GC::Ref<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(GC::Ref<Node> root)
 | ||
| {
 | ||
|     // AD-HOC: This method iterates over the root's entire subtree. That iteration does nothing if the root is not a
 | ||
|     //         shadow root (see `find_slottables`). This iteration can be very expensive as the HTML parser inserts
 | ||
|     //         nodes, especially on sites with many elements. So we skip it if we know it's going to be a no-op anyways.
 | ||
|     if (!root->is_shadow_root())
 | ||
|         return;
 | ||
| 
 | ||
|     // 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 TraversalDecision::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(GC::Ref<HTML::HTMLSlotElement> slottable)
 | ||
| {
 | ||
|     // 1. Append slot to slot’s relevant agent’s signal slots.
 | ||
|     HTML::relevant_similar_origin_window_agent(slottable).signal_slots.append(slottable);
 | ||
| 
 | ||
|     // 2. Queue a mutation observer microtask.
 | ||
|     Bindings::queue_mutation_observer_microtask(slottable->document());
 | ||
| }
 | ||
| 
 | ||
| }
 |