ladybird/Libraries/LibWeb/HTML/HTMLSlotElement.cpp
Tim Ledbetter 18b8ba1fd3 LibWeb: Avoid subtree iteration when assigning slottables
This change introduces SlotRegistry to track slot elements per shadow
root. This allows us to iterate slots directly when assigning
slottables for a tree instead of walking an entire subtree.
2026-01-31 11:44:20 +01:00

176 lines
6.3 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2020, the SerenityOS developers.
* 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/HTMLSlotElementPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/DOM/Text.h>
#include <LibWeb/HTML/HTMLSlotElement.h>
namespace Web::HTML {
GC_DEFINE_ALLOCATOR(HTMLSlotElement);
HTMLSlotElement::HTMLSlotElement(DOM::Document& document, DOM::QualifiedName qualified_name)
: HTMLElement(document, move(qualified_name))
{
}
HTMLSlotElement::~HTMLSlotElement() = default;
void HTMLSlotElement::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLSlotElement);
Base::initialize(realm);
}
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<GC::Root<DOM::Node>> HTMLSlotElement::assigned_nodes(AssignedNodesOptions options) const
{
// 1. If options["flatten"] is false, then return this's assigned nodes.
if (!options.flatten) {
Vector<GC::Root<DOM::Node>> assigned_nodes;
assigned_nodes.ensure_capacity(assigned_nodes_internal().size());
for (auto const& node : assigned_nodes_internal()) {
node.visit([&](auto const& node) {
assigned_nodes.unchecked_append(*node);
});
}
return assigned_nodes;
}
// 2. Return the result of finding flattened slottables with this.
// FIXME: Make this a lot less awkward!
auto nodes = DOM::find_flattened_slottables(const_cast<HTMLSlotElement&>(*this));
Vector<GC::Root<DOM::Node>> assigned_nodes;
assigned_nodes.ensure_capacity(nodes.size());
for (auto const& node : nodes) {
node.visit([&](auto const& slottable) {
assigned_nodes.unchecked_append(*slottable);
});
}
return assigned_nodes;
}
// https://html.spec.whatwg.org/multipage/scripting.html#dom-slot-assignedelements
Vector<GC::Root<DOM::Element>> HTMLSlotElement::assigned_elements(AssignedNodesOptions options) const
{
// 1. If options["flatten"] is false, then return this's assigned nodes, filtered to contain only Element nodes.
if (!options.flatten) {
Vector<GC::Root<DOM::Element>> assigned_nodes;
for (auto const& node : assigned_nodes_internal()) {
if (auto const* element = node.get_pointer<GC::Ref<DOM::Element>>())
assigned_nodes.append(*element);
}
return assigned_nodes;
}
// 2. Return the result of finding flattened slottables with this, filtered to contain only Element nodes.
auto result = DOM::find_flattened_slottables(const_cast<HTMLSlotElement&>(*this));
Vector<GC::Root<DOM::Element>> assigned_nodes;
for (auto const& node : result) {
if (auto const* element = node.get_pointer<GC::Ref<DOM::Element>>())
assigned_nodes.append(*element);
}
return assigned_nodes;
}
// 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());
}
// https://dom.spec.whatwg.org/#ref-for-concept-element-attributes-change-ext
void HTMLSlotElement::attribute_changed(FlyString const& local_name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_)
{
Base::attribute_changed(local_name, old_value, value, namespace_);
// 1. If element is a slot, localName is name, and namespace is null, then:
if (local_name == AttributeNames::name && !namespace_.has_value()) {
// 1. If value is oldValue, then return.
if (value == old_value)
return;
// 2. If value is null and oldValue is the empty string, then return.
if (!value.has_value() && old_value == String {})
return;
// 3. If value is the empty string and oldValue is null, then return.
if (value == String {} && !old_value.has_value())
return;
// OPTIMIZATION: Update the slot registry before changing the name.
auto* shadow_root = as_if<DOM::ShadowRoot>(root());
if (shadow_root)
shadow_root->unregister_slot(*this);
// 4. If value is null or the empty string, then set elements name to the empty string.
if (!value.has_value())
set_slot_name({});
// 5. Otherwise, set elements name to value.
else
set_slot_name(*value);
// OPTIMIZATION: Register the slot with its new name.
if (shadow_root)
shadow_root->register_slot(*this);
// 6. Run assign slottables for a tree with elements root.
DOM::assign_slottables_for_a_tree(root());
}
}
}