ladybird/Userland/Libraries/LibWeb/DOM/NamedNodeMap.cpp
2022-09-06 00:27:09 +02:00

243 lines
8.7 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) 2021, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/DOM/Attribute.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/NamedNodeMap.h>
#include <LibWeb/Namespace.h>
namespace Web::DOM {
JS::NonnullGCPtr<NamedNodeMap> NamedNodeMap::create(Element& element)
{
auto& realm = element.realm();
return *realm.heap().allocate<NamedNodeMap>(realm, element);
}
NamedNodeMap::NamedNodeMap(Element& element)
: Bindings::LegacyPlatformObject(element.window().cached_web_prototype("NamedNodeMap"))
, m_element(element)
{
}
void NamedNodeMap::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_element.ptr());
for (auto& attribute : m_attributes)
visitor.visit(attribute.ptr());
}
// https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-indices%E2%91%A3
bool NamedNodeMap::is_supported_property_index(u32 index) const
{
return index < m_attributes.size();
}
// https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-names%E2%91%A0
Vector<String> NamedNodeMap::supported_property_names() const
{
// 1. Let names be the qualified names of the attributes in this NamedNodeMap objects attribute list, with duplicates omitted, in order.
Vector<String> names;
names.ensure_capacity(m_attributes.size());
for (auto const& attribute : m_attributes) {
if (!names.contains_slow(attribute->name()))
names.append(attribute->name());
}
// 2. If this NamedNodeMap objects element is in the HTML namespace and its node document is an HTML document, then for each name in names:
// FIXME: Handle the second condition, assume it is an HTML document for now.
if (associated_element().namespace_uri() == Namespace::HTML) {
// 1. Let lowercaseName be name, in ASCII lowercase.
// 2. If lowercaseName is not equal to name, remove name from names.
names.remove_all_matching([](auto const& name) { return name != name.to_lowercase(); });
}
// 3. Return names.
return names;
}
// https://dom.spec.whatwg.org/#dom-namednodemap-item
Attribute const* NamedNodeMap::item(u32 index) const
{
// 1. If index is equal to or greater than thiss attribute lists size, then return null.
if (index >= m_attributes.size())
return nullptr;
// 2. Otherwise, return thiss attribute list[index].
return m_attributes[index].ptr();
}
// https://dom.spec.whatwg.org/#dom-namednodemap-getnameditem
Attribute const* NamedNodeMap::get_named_item(StringView qualified_name) const
{
return get_attribute(qualified_name);
}
// https://dom.spec.whatwg.org/#dom-namednodemap-setnameditem
ExceptionOr<Attribute const*> NamedNodeMap::set_named_item(Attribute& attribute)
{
return set_attribute(attribute);
}
// https://dom.spec.whatwg.org/#dom-namednodemap-removenameditem
ExceptionOr<Attribute const*> NamedNodeMap::remove_named_item(StringView qualified_name)
{
// 1. Let attr be the result of removing an attribute given qualifiedName and element.
auto const* attribute = remove_attribute(qualified_name);
// 2. If attr is null, then throw a "NotFoundError" DOMException.
if (!attribute)
return NotFoundError::create(global_object(), String::formatted("Attribute with name '{}' not found", qualified_name));
// 3. Return attr.
return nullptr;
}
// https://dom.spec.whatwg.org/#concept-element-attributes-get-by-name
Attribute* NamedNodeMap::get_attribute(StringView qualified_name, size_t* item_index)
{
return const_cast<Attribute*>(const_cast<NamedNodeMap const*>(this)->get_attribute(qualified_name, item_index));
}
// https://dom.spec.whatwg.org/#concept-element-attributes-get-by-name
Attribute const* NamedNodeMap::get_attribute(StringView qualified_name, size_t* item_index) const
{
if (item_index)
*item_index = 0;
// 1. If element is in the HTML namespace and its node document is an HTML document, then set qualifiedName to qualifiedName in ASCII lowercase.
// FIXME: Handle the second condition, assume it is an HTML document for now.
bool compare_as_lowercase = associated_element().namespace_uri() == Namespace::HTML;
// 2. Return the first attribute in elements attribute list whose qualified name is qualifiedName; otherwise null.
for (auto const& attribute : m_attributes) {
if (compare_as_lowercase) {
if (attribute->name().equals_ignoring_case(qualified_name))
return attribute.ptr();
} else {
if (attribute->name() == qualified_name)
return attribute.ptr();
}
if (item_index)
++(*item_index);
}
return nullptr;
}
// https://dom.spec.whatwg.org/#concept-element-attributes-set
ExceptionOr<Attribute const*> NamedNodeMap::set_attribute(Attribute& attribute)
{
// 1. If attrs element is neither null nor element, throw an "InUseAttributeError" DOMException.
if ((attribute.owner_element() != nullptr) && (attribute.owner_element() != &associated_element()))
return InUseAttributeError::create(global_object(), "Attribute must not already be in use"sv);
// 2. Let oldAttr be the result of getting an attribute given attrs namespace, attrs local name, and element.
// FIXME: When getNamedItemNS is implemented, use that instead.
size_t old_attribute_index = 0;
auto* old_attribute = get_attribute(attribute.local_name(), &old_attribute_index);
// 3. If oldAttr is attr, return attr.
if (old_attribute == &attribute)
return &attribute;
// 4. If oldAttr is non-null, then replace oldAttr with attr.
if (old_attribute) {
replace_attribute(*old_attribute, attribute, old_attribute_index);
}
// 5. Otherwise, append attr to element.
else {
append_attribute(attribute);
}
// 6. Return oldAttr.
return old_attribute;
}
// https://dom.spec.whatwg.org/#concept-element-attributes-replace
void NamedNodeMap::replace_attribute(Attribute& old_attribute, Attribute& new_attribute, size_t old_attribute_index)
{
// 1. Handle attribute changes for oldAttr with oldAttrs element, oldAttrs value, and newAttrs value.
VERIFY(old_attribute.owner_element());
old_attribute.handle_attribute_changes(*old_attribute.owner_element(), old_attribute.value(), new_attribute.value());
// 2. Replace oldAttr by newAttr in oldAttrs elements attribute list.
m_attributes.remove(old_attribute_index);
m_attributes.insert(old_attribute_index, new_attribute);
// 3. Set newAttrs element to oldAttrs element.
new_attribute.set_owner_element(old_attribute.owner_element());
// 4 .Set oldAttrs element to null.
old_attribute.set_owner_element(nullptr);
}
// https://dom.spec.whatwg.org/#concept-element-attributes-append
void NamedNodeMap::append_attribute(Attribute& attribute)
{
// 1. Handle attribute changes for attribute with element, null, and attributes value.
attribute.handle_attribute_changes(associated_element(), {}, attribute.value());
// 2. Append attribute to elements attribute list.
m_attributes.append(attribute);
// 3. Set attributes element to element.
attribute.set_owner_element(&associated_element());
}
// https://dom.spec.whatwg.org/#concept-element-attributes-remove
void NamedNodeMap::remove_attribute_at_index(size_t attribute_index)
{
JS::NonnullGCPtr<Attribute> attribute = m_attributes.at(attribute_index);
// 1. Handle attribute changes for attribute with attributes element, attributes value, and null.
VERIFY(attribute->owner_element());
attribute->handle_attribute_changes(*attribute->owner_element(), attribute->value(), {});
// 2. Remove attribute from attributes elements attribute list.
m_attributes.remove(attribute_index);
// 3. Set attributes element to null.
attribute->set_owner_element(nullptr);
}
// https://dom.spec.whatwg.org/#concept-element-attributes-remove-by-name
Attribute const* NamedNodeMap::remove_attribute(StringView qualified_name)
{
size_t item_index = 0;
// 1. Let attr be the result of getting an attribute given qualifiedName and element.
auto const* attribute = get_attribute(qualified_name, &item_index);
// 2. If attr is non-null, then remove attr.
if (attribute)
remove_attribute_at_index(item_index);
// 3. Return attr.
return attribute;
}
JS::Value NamedNodeMap::item_value(size_t index) const
{
auto const* node = item(index);
if (!node)
return JS::js_undefined();
return node;
}
JS::Value NamedNodeMap::named_item_value(FlyString const& name) const
{
auto const* node = get_named_item(name);
if (!node)
return JS::js_undefined();
return node;
}
}