Serenity Operating System
at master 320 lines 12 kB view raw
1/* 2 * Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org> 3 * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> 4 * Copyright (c) 2022, Alexander Narsudinov <a.narsudinov@gmail.com> 5 * 6 * SPDX-License-Identifier: BSD-2-Clause 7 */ 8 9#include <LibWeb/DOM/Attr.h> 10#include <LibWeb/DOM/Document.h> 11#include <LibWeb/DOM/NamedNodeMap.h> 12#include <LibWeb/Namespace.h> 13 14namespace Web::DOM { 15 16WebIDL::ExceptionOr<JS::NonnullGCPtr<NamedNodeMap>> NamedNodeMap::create(Element& element) 17{ 18 auto& realm = element.realm(); 19 return MUST_OR_THROW_OOM(realm.heap().allocate<NamedNodeMap>(realm, element)); 20} 21 22NamedNodeMap::NamedNodeMap(Element& element) 23 : Bindings::LegacyPlatformObject(element.realm()) 24 , m_element(element) 25{ 26} 27 28JS::ThrowCompletionOr<void> NamedNodeMap::initialize(JS::Realm& realm) 29{ 30 MUST_OR_THROW_OOM(Base::initialize(realm)); 31 set_prototype(&Bindings::ensure_web_prototype<Bindings::NamedNodeMapPrototype>(realm, "NamedNodeMap")); 32 33 return {}; 34} 35 36void NamedNodeMap::visit_edges(Cell::Visitor& visitor) 37{ 38 Base::visit_edges(visitor); 39 visitor.visit(m_element.ptr()); 40 for (auto& attribute : m_attributes) 41 visitor.visit(attribute.ptr()); 42} 43 44// https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-indices%E2%91%A3 45bool NamedNodeMap::is_supported_property_index(u32 index) const 46{ 47 return index < m_attributes.size(); 48} 49 50// https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-names%E2%91%A0 51Vector<DeprecatedString> NamedNodeMap::supported_property_names() const 52{ 53 // 1. Let names be the qualified names of the attributes in this NamedNodeMap object’s attribute list, with duplicates omitted, in order. 54 Vector<DeprecatedString> names; 55 names.ensure_capacity(m_attributes.size()); 56 57 for (auto const& attribute : m_attributes) { 58 if (!names.contains_slow(attribute->name())) 59 names.append(attribute->name()); 60 } 61 62 // 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: 63 // FIXME: Handle the second condition, assume it is an HTML document for now. 64 if (associated_element().namespace_uri() == Namespace::HTML) { 65 // 1. Let lowercaseName be name, in ASCII lowercase. 66 // 2. If lowercaseName is not equal to name, remove name from names. 67 names.remove_all_matching([](auto const& name) { return name != name.to_lowercase(); }); 68 } 69 70 // 3. Return names. 71 return names; 72} 73 74// https://dom.spec.whatwg.org/#dom-namednodemap-item 75Attr const* NamedNodeMap::item(u32 index) const 76{ 77 // 1. If index is equal to or greater than this’s attribute list’s size, then return null. 78 if (index >= m_attributes.size()) 79 return nullptr; 80 81 // 2. Otherwise, return this’s attribute list[index]. 82 return m_attributes[index].ptr(); 83} 84 85// https://dom.spec.whatwg.org/#dom-namednodemap-getnameditem 86Attr const* NamedNodeMap::get_named_item(StringView qualified_name) const 87{ 88 return get_attribute(qualified_name); 89} 90 91// https://dom.spec.whatwg.org/#dom-namednodemap-getnameditemns 92Attr const* NamedNodeMap::get_named_item_ns(StringView namespace_, StringView local_name) const 93{ 94 return get_attribute_ns(namespace_, local_name); 95} 96 97// https://dom.spec.whatwg.org/#dom-namednodemap-setnameditem 98WebIDL::ExceptionOr<JS::GCPtr<Attr>> NamedNodeMap::set_named_item(Attr& attribute) 99{ 100 return set_attribute(attribute); 101} 102 103// https://dom.spec.whatwg.org/#dom-namednodemap-setnameditemns 104WebIDL::ExceptionOr<JS::GCPtr<Attr>> NamedNodeMap::set_named_item_ns(Attr& attribute) 105{ 106 return set_attribute(attribute); 107} 108 109// https://dom.spec.whatwg.org/#dom-namednodemap-removenameditem 110WebIDL::ExceptionOr<Attr const*> NamedNodeMap::remove_named_item(StringView qualified_name) 111{ 112 // 1. Let attr be the result of removing an attribute given qualifiedName and element. 113 auto const* attribute = remove_attribute(qualified_name); 114 115 // 2. If attr is null, then throw a "NotFoundError" DOMException. 116 if (!attribute) 117 return WebIDL::NotFoundError::create(realm(), DeprecatedString::formatted("Attribute with name '{}' not found", qualified_name)); 118 119 // 3. Return attr. 120 return attribute; 121} 122 123// https://dom.spec.whatwg.org/#dom-namednodemap-removenameditemns 124WebIDL::ExceptionOr<Attr const*> NamedNodeMap::remove_named_item_ns(StringView namespace_, StringView local_name) 125{ 126 // 1. Let attr be the result of removing an attribute given namespace, localName, and element. 127 auto const* attribute = remove_attribute_ns(namespace_, local_name); 128 129 // 2. If attr is null, then throw a "NotFoundError" DOMException. 130 if (!attribute) 131 return WebIDL::NotFoundError::create(realm(), DeprecatedString::formatted("Attribute with namespace '{}' and local name '{}' not found", namespace_, local_name)); 132 133 // 3. Return attr. 134 return attribute; 135} 136 137// https://dom.spec.whatwg.org/#concept-element-attributes-get-by-name 138Attr* NamedNodeMap::get_attribute(StringView qualified_name, size_t* item_index) 139{ 140 return const_cast<Attr*>(const_cast<NamedNodeMap const*>(this)->get_attribute(qualified_name, item_index)); 141} 142 143// https://dom.spec.whatwg.org/#concept-element-attributes-get-by-name 144Attr const* NamedNodeMap::get_attribute(StringView qualified_name, size_t* item_index) const 145{ 146 if (item_index) 147 *item_index = 0; 148 149 // 1. If element is in the HTML namespace and its node document is an HTML document, then set qualifiedName to qualifiedName in ASCII lowercase. 150 // FIXME: Handle the second condition, assume it is an HTML document for now. 151 bool compare_as_lowercase = associated_element().namespace_uri() == Namespace::HTML; 152 153 // 2. Return the first attribute in element’s attribute list whose qualified name is qualifiedName; otherwise null. 154 for (auto const& attribute : m_attributes) { 155 if (compare_as_lowercase) { 156 if (attribute->name().equals_ignoring_ascii_case(qualified_name)) 157 return attribute.ptr(); 158 } else { 159 if (attribute->name() == qualified_name) 160 return attribute.ptr(); 161 } 162 163 if (item_index) 164 ++(*item_index); 165 } 166 167 return nullptr; 168} 169 170// https://dom.spec.whatwg.org/#concept-element-attributes-get-by-namespace 171Attr* NamedNodeMap::get_attribute_ns(StringView namespace_, StringView local_name, size_t* item_index) 172{ 173 return const_cast<Attr*>(const_cast<NamedNodeMap const*>(this)->get_attribute_ns(namespace_, local_name, item_index)); 174} 175 176// https://dom.spec.whatwg.org/#concept-element-attributes-get-by-namespace 177Attr const* NamedNodeMap::get_attribute_ns(StringView namespace_, StringView local_name, size_t* item_index) const 178{ 179 if (item_index) 180 *item_index = 0; 181 182 // 1. If namespace is the empty string, then set it to null. 183 if (namespace_.is_empty()) 184 namespace_ = {}; 185 186 // 2. Return the attribute in element’s attribute list whose namespace is namespace and local name is localName, if any; otherwise null. 187 for (auto const& attribute : m_attributes) { 188 if (attribute->namespace_uri() == namespace_ && attribute->local_name() == local_name) 189 return attribute.ptr(); 190 if (item_index) 191 ++(*item_index); 192 } 193 194 return nullptr; 195} 196 197// https://dom.spec.whatwg.org/#concept-element-attributes-set 198WebIDL::ExceptionOr<JS::GCPtr<Attr>> NamedNodeMap::set_attribute(Attr& attribute) 199{ 200 // 1. If attr’s element is neither null nor element, throw an "InUseAttributeError" DOMException. 201 if ((attribute.owner_element() != nullptr) && (attribute.owner_element() != &associated_element())) 202 return WebIDL::InUseAttributeError::create(realm(), "Attribute must not already be in use"sv); 203 204 // 2. Let oldAttr be the result of getting an attribute given attr’s namespace, attr’s local name, and element. 205 size_t old_attribute_index = 0; 206 auto* old_attribute = get_attribute_ns(attribute.namespace_uri(), attribute.local_name(), &old_attribute_index); 207 208 // 3. If oldAttr is attr, return attr. 209 if (old_attribute == &attribute) 210 return &attribute; 211 212 // 4. If oldAttr is non-null, then replace oldAttr with attr. 213 if (old_attribute) { 214 replace_attribute(*old_attribute, attribute, old_attribute_index); 215 } 216 // 5. Otherwise, append attr to element. 217 else { 218 append_attribute(attribute); 219 } 220 221 // 6. Return oldAttr. 222 return old_attribute; 223} 224 225// https://dom.spec.whatwg.org/#concept-element-attributes-replace 226void NamedNodeMap::replace_attribute(Attr& old_attribute, Attr& new_attribute, size_t old_attribute_index) 227{ 228 // 1. Handle attribute changes for oldAttr with oldAttr’s element, oldAttr’s value, and newAttr’s value. 229 VERIFY(old_attribute.owner_element()); 230 old_attribute.handle_attribute_changes(*old_attribute.owner_element(), old_attribute.value(), new_attribute.value()); 231 232 // 2. Replace oldAttr by newAttr in oldAttr’s element’s attribute list. 233 m_attributes.remove(old_attribute_index); 234 m_attributes.insert(old_attribute_index, new_attribute); 235 236 // 3. Set newAttr’s element to oldAttr’s element. 237 new_attribute.set_owner_element(old_attribute.owner_element()); 238 239 // 4 .Set oldAttr’s element to null. 240 old_attribute.set_owner_element(nullptr); 241} 242 243// https://dom.spec.whatwg.org/#concept-element-attributes-append 244void NamedNodeMap::append_attribute(Attr& attribute) 245{ 246 // 1. Handle attribute changes for attribute with element, null, and attribute’s value. 247 attribute.handle_attribute_changes(associated_element(), {}, attribute.value()); 248 249 // 2. Append attribute to element’s attribute list. 250 m_attributes.append(attribute); 251 252 // 3. Set attribute’s element to element. 253 attribute.set_owner_element(&associated_element()); 254} 255 256// https://dom.spec.whatwg.org/#concept-element-attributes-remove 257void NamedNodeMap::remove_attribute_at_index(size_t attribute_index) 258{ 259 JS::NonnullGCPtr<Attr> attribute = m_attributes.at(attribute_index); 260 261 // 1. Handle attribute changes for attribute with attribute’s element, attribute’s value, and null. 262 VERIFY(attribute->owner_element()); 263 attribute->handle_attribute_changes(*attribute->owner_element(), attribute->value(), {}); 264 265 // 2. Remove attribute from attribute’s element’s attribute list. 266 m_attributes.remove(attribute_index); 267 268 // 3. Set attribute’s element to null. 269 attribute->set_owner_element(nullptr); 270} 271 272// https://dom.spec.whatwg.org/#concept-element-attributes-remove-by-name 273Attr const* NamedNodeMap::remove_attribute(StringView qualified_name) 274{ 275 size_t item_index = 0; 276 277 // 1. Let attr be the result of getting an attribute given qualifiedName and element. 278 auto const* attribute = get_attribute(qualified_name, &item_index); 279 280 // 2. If attr is non-null, then remove attr. 281 if (attribute) 282 remove_attribute_at_index(item_index); 283 284 // 3. Return attr. 285 return attribute; 286} 287 288// https://dom.spec.whatwg.org/#concept-element-attributes-remove-by-namespace 289Attr const* NamedNodeMap::remove_attribute_ns(StringView namespace_, StringView local_name) 290{ 291 size_t item_index = 0; 292 293 // 1. Let attr be the result of getting an attribute given namespace, localName, and element. 294 auto const* attribute = get_attribute_ns(namespace_, local_name, &item_index); 295 296 // 2. If attr is non-null, then remove attr. 297 if (attribute) 298 remove_attribute_at_index(item_index); 299 300 // 3. Return attr. 301 return attribute; 302} 303 304WebIDL::ExceptionOr<JS::Value> NamedNodeMap::item_value(size_t index) const 305{ 306 auto const* node = item(index); 307 if (!node) 308 return JS::js_undefined(); 309 return node; 310} 311 312WebIDL::ExceptionOr<JS::Value> NamedNodeMap::named_item_value(DeprecatedFlyString const& name) const 313{ 314 auto const* node = get_named_item(name); 315 if (!node) 316 return JS::js_undefined(); 317 return node; 318} 319 320}