From 2a3ac02ef1e84e0b772dff99cf5f2e66c9e1fe7e Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sat, 16 Oct 2021 15:30:21 -0400 Subject: [PATCH] LibWeb: Implement (most of) NamedNodeMap to store attributes --- .../LibWeb/WrapperGenerator.cpp | 4 + Userland/Libraries/LibWeb/CMakeLists.txt | 2 + .../Libraries/LibWeb/DOM/NamedNodeMap.cpp | 199 ++++++++++++++++++ Userland/Libraries/LibWeb/DOM/NamedNodeMap.h | 63 ++++++ .../Libraries/LibWeb/DOM/NamedNodeMap.idl | 14 ++ Userland/Libraries/LibWeb/Forward.h | 2 + 6 files changed, 284 insertions(+) create mode 100644 Userland/Libraries/LibWeb/DOM/NamedNodeMap.cpp create mode 100644 Userland/Libraries/LibWeb/DOM/NamedNodeMap.h create mode 100644 Userland/Libraries/LibWeb/DOM/NamedNodeMap.idl diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp index d9dcec0c28..2d0a5d595c 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp @@ -928,6 +928,8 @@ static bool is_wrappable_type(IDL::Type const& type) return true; if (type.name == "Attribute") return true; + if (type.name == "NamedNodeMap") + return true; return false; } @@ -1655,6 +1657,7 @@ void generate_implementation(IDL::Interface const& interface) #include #include #include +#include #include #include #include @@ -2867,6 +2870,7 @@ void generate_prototype_implementation(IDL::Interface const& interface) #include #include #include +#include #include #include #include diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index f16da893c3..92028e30f6 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -73,6 +73,7 @@ set(SOURCES DOM/EventTarget.cpp DOM/HTMLCollection.cpp DOM/LiveNodeList.cpp + DOM/NamedNodeMap.cpp DOM/Node.cpp DOM/ParentNode.cpp DOM/Position.cpp @@ -383,6 +384,7 @@ libweb_js_wrapper(DOM/Element) libweb_js_wrapper(DOM/Event) libweb_js_wrapper(DOM/EventTarget) libweb_js_wrapper(DOM/HTMLCollection) +libweb_js_wrapper(DOM/NamedNodeMap) libweb_js_wrapper(DOM/Node) libweb_js_wrapper(DOM/NodeList) libweb_js_wrapper(DOM/ProcessingInstruction) diff --git a/Userland/Libraries/LibWeb/DOM/NamedNodeMap.cpp b/Userland/Libraries/LibWeb/DOM/NamedNodeMap.cpp new file mode 100644 index 0000000000..5d118ab483 --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/NamedNodeMap.cpp @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2021, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Web::DOM { + +NonnullRefPtr NamedNodeMap::create(Element const& associated_element) +{ + return adopt_ref(*new NamedNodeMap(associated_element)); +} + +NamedNodeMap::NamedNodeMap(Element const& associated_element) + : m_associated_element(associated_element) +{ +} + +// 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 NamedNodeMap::supported_property_names() const +{ + // 1. Let names be the qualified names of the attributes in this NamedNodeMap object’s attribute list, with duplicates omitted, in order. + Vector 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 object’s 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 (m_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 this’s attribute list’s size, then return null. + if (index >= m_attributes.size()) + return nullptr; + + // 2. Otherwise, return this’s attribute list[index]. + return &m_attributes[index]; +} + +// 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 NamedNodeMap::set_named_item(Attribute& attribute) +{ + return set_attribute(attribute); +} + +// https://dom.spec.whatwg.org/#dom-namednodemap-removenameditem +ExceptionOr 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(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(const_cast(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. + Optional qualified_name_lowercase; + if (m_associated_element.namespace_uri() == Namespace::HTML) { + qualified_name_lowercase = qualified_name.to_lowercase_string(); + qualified_name = qualified_name_lowercase->view(); + } + + // 2. Return the first attribute in element’s attribute list whose qualified name is qualifiedName; otherwise null. + for (auto const& attribute : m_attributes) { + if (attribute.name() == qualified_name) + return &attribute; + if (item_index) + ++(*item_index); + } + + return nullptr; +} + +// https://dom.spec.whatwg.org/#concept-element-attributes-set +ExceptionOr NamedNodeMap::set_attribute(Attribute& attribute) +{ + // 1. If attr’s element is neither null nor element, throw an "InUseAttributeError" DOMException. + if ((attribute.owner_element() != nullptr) && (attribute.owner_element() != &m_associated_element)) + return InUseAttributeError::create("Attribute must not already be in use"sv); + + // 2. Let oldAttr be the result of getting an attribute given attr’s namespace, attr’s 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 oldAttr’s element, oldAttr’s value, and newAttr’s value. + // FIXME: The steps to handle an attribute change deal with mutation records and custom element states. + // Once those are supported, implement these steps: https://dom.spec.whatwg.org/#handle-attribute-changes + + // 2. Replace oldAttr by newAttr in oldAttr’s element’s attribute list. + m_attributes.remove(old_attribute_index); + m_attributes.insert(old_attribute_index, new_attribute); + + // 3. Set newAttr’s element to oldAttr’s element. + new_attribute.set_owner_element(old_attribute.owner_element()); + + // 4 .Set oldAttr’s 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 attribute’s value. + // FIXME: The steps to handle an attribute change deal with mutation records and custom element states. + // Once those are supported, implement these steps: https://dom.spec.whatwg.org/#handle-attribute-changes + + // 2. Append attribute to element’s attribute list. + m_attributes.append(attribute); + + // 3. Set attribute’s element to element. + attribute.set_owner_element(&m_associated_element); +} + +// 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) + m_attributes.remove(item_index); + + // 3. Return attr. + return attribute; +} + +} diff --git a/Userland/Libraries/LibWeb/DOM/NamedNodeMap.h b/Userland/Libraries/LibWeb/DOM/NamedNodeMap.h new file mode 100644 index 0000000000..b07b999bd1 --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/NamedNodeMap.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace Web::DOM { + +// https://dom.spec.whatwg.org/#interface-namednodemap +class NamedNodeMap final + : public RefCounted + , public Bindings::Wrappable { + +public: + using WrapperType = Bindings::NamedNodeMapWrapper; + + static NonnullRefPtr create(Element const& associated_element); + ~NamedNodeMap() = default; + + bool is_supported_property_index(u32 index) const; + Vector supported_property_names() const; + + size_t length() const { return m_attributes.size(); } + bool is_empty() const { return m_attributes.is_empty(); } + + // Methods defined by the spec for JavaScript: + Attribute const* item(u32 index) const; + Attribute const* get_named_item(StringView qualified_name) const; + ExceptionOr set_named_item(Attribute& attribute); + ExceptionOr remove_named_item(StringView qualified_name); + + // Methods defined by the spec for internal use: + Attribute* get_attribute(StringView qualified_name, size_t* item_index = nullptr); + Attribute const* get_attribute(StringView qualified_name, size_t* item_index = nullptr) const; + ExceptionOr set_attribute(Attribute& attribute); + void replace_attribute(Attribute& old_attribute, Attribute& new_attribute, size_t old_attribute_index); + void append_attribute(Attribute& attribute); + Attribute const* remove_attribute(StringView qualified_name); + +private: + explicit NamedNodeMap(Element const& associated_element); + + Element const& m_associated_element; + NonnullRefPtrVector m_attributes; +}; + +} + +namespace Web::Bindings { + +NamedNodeMapWrapper* wrap(JS::GlobalObject&, DOM::NamedNodeMap&); + +} diff --git a/Userland/Libraries/LibWeb/DOM/NamedNodeMap.idl b/Userland/Libraries/LibWeb/DOM/NamedNodeMap.idl new file mode 100644 index 0000000000..68f889a5bb --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/NamedNodeMap.idl @@ -0,0 +1,14 @@ +[Exposed=Window, LegacyUnenumerableNamedProperties] +interface NamedNodeMap { + readonly attribute unsigned long length; + + getter Attribute? item(unsigned long index); + getter Attribute? getNamedItem(DOMString qualifiedName); + // Attribute? getNamedItemNS(DOMString? namespace, DOMString localName); + + [CEReactions] Attribute? setNamedItem(Attribute attr); + // [CEReactions] Attribute? setNamedItemNS(Attribute attr); + + [CEReactions] Attribute removeNamedItem(DOMString qualifiedName); + // [CEReactions] Attribute removeNamedItemNS(DOMString? namespace, DOMString localName); +}; diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 2f3dd779dd..791a3b0fc3 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -88,6 +88,7 @@ class EventListener; class EventTarget; class HTMLCollection; class LiveNodeList; +class NamedNodeMap; class Node; class NodeList; class ParentNode; @@ -400,6 +401,7 @@ class MessageChannelWrapper; class MessageEventWrapper; class MessagePortWrapper; class MouseEventWrapper; +class NamedNodeMapWrapper; class NodeListWrapper; class NodeWrapper; class PageTransitionEventWrapper;