Serenity Operating System
at master 203 lines 8.4 kB view raw
1/* 2 * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/CharacterTypes.h> 8#include <LibWeb/Bindings/Intrinsics.h> 9#include <LibWeb/DOM/Document.h> 10#include <LibWeb/DOM/Element.h> 11#include <LibWeb/HTML/DOMStringMap.h> 12 13namespace Web::HTML { 14 15WebIDL::ExceptionOr<JS::NonnullGCPtr<DOMStringMap>> DOMStringMap::create(DOM::Element& element) 16{ 17 auto& realm = element.realm(); 18 return MUST_OR_THROW_OOM(realm.heap().allocate<DOMStringMap>(realm, element)); 19} 20 21DOMStringMap::DOMStringMap(DOM::Element& element) 22 : LegacyPlatformObject(element.realm()) 23 , m_associated_element(element) 24{ 25} 26 27DOMStringMap::~DOMStringMap() = default; 28 29JS::ThrowCompletionOr<void> DOMStringMap::initialize(JS::Realm& realm) 30{ 31 MUST_OR_THROW_OOM(Base::initialize(realm)); 32 set_prototype(&Bindings::ensure_web_prototype<Bindings::DOMStringMapPrototype>(realm, "DOMStringMap")); 33 34 return {}; 35} 36 37void DOMStringMap::visit_edges(Cell::Visitor& visitor) 38{ 39 Base::visit_edges(visitor); 40 visitor.visit(m_associated_element.ptr()); 41} 42 43// https://html.spec.whatwg.org/multipage/dom.html#concept-domstringmap-pairs 44Vector<DOMStringMap::NameValuePair> DOMStringMap::get_name_value_pairs() const 45{ 46 // 1. Let list be an empty list of name-value pairs. 47 Vector<NameValuePair> list; 48 49 // 2. For each content attribute on the DOMStringMap's associated element whose first five characters are the string "data-" and whose remaining characters (if any) do not include any ASCII upper alphas, 50 // in the order that those attributes are listed in the element's attribute list, add a name-value pair to list whose name is the attribute's name with the first five characters removed and whose value 51 // is the attribute's value. 52 m_associated_element->for_each_attribute([&](auto& name, auto& value) { 53 if (!name.starts_with("data-"sv)) 54 return; 55 56 auto name_after_starting_data = name.view().substring_view(5); 57 58 for (auto character : name_after_starting_data) { 59 if (is_ascii_upper_alpha(character)) 60 return; 61 } 62 63 // 3. For each name in list, for each U+002D HYPHEN-MINUS character (-) in the name that is followed by an ASCII lower alpha, remove the U+002D HYPHEN-MINUS character (-) and replace the character 64 // that followed it by the same character converted to ASCII uppercase. 65 StringBuilder builder; 66 for (size_t character_index = 0; character_index < name_after_starting_data.length(); ++character_index) { 67 auto current_character = name_after_starting_data[character_index]; 68 69 if (character_index + 1 < name_after_starting_data.length() && current_character == '-') { 70 auto next_character = name_after_starting_data[character_index + 1]; 71 72 if (is_ascii_lower_alpha(next_character)) { 73 builder.append(to_ascii_uppercase(next_character)); 74 75 // Skip the next character 76 ++character_index; 77 78 continue; 79 } 80 } 81 82 builder.append(current_character); 83 } 84 85 list.append({ builder.to_deprecated_string(), value }); 86 }); 87 88 // 4. Return list. 89 return list; 90} 91 92// https://html.spec.whatwg.org/multipage/dom.html#concept-domstringmap-pairs 93// NOTE: There isn't a direct link to this, so the link is to one of the algorithms above it. 94Vector<DeprecatedString> DOMStringMap::supported_property_names() const 95{ 96 // The supported property names on a DOMStringMap object at any instant are the names of each pair returned from getting the DOMStringMap's name-value pairs at that instant, in the order returned. 97 Vector<DeprecatedString> names; 98 auto name_value_pairs = get_name_value_pairs(); 99 for (auto& name_value_pair : name_value_pairs) { 100 names.append(name_value_pair.name); 101 } 102 return names; 103} 104 105// https://html.spec.whatwg.org/multipage/dom.html#dom-domstringmap-nameditem 106DeprecatedString DOMStringMap::determine_value_of_named_property(DeprecatedString const& name) const 107{ 108 // To determine the value of a named property name for a DOMStringMap, return the value component of the name-value pair whose name component is name in the list returned from getting the 109 // DOMStringMap's name-value pairs. 110 auto name_value_pairs = get_name_value_pairs(); 111 auto optional_value = name_value_pairs.first_matching([&name](NameValuePair& name_value_pair) { 112 return name_value_pair.name == name; 113 }); 114 115 // NOTE: determine_value_of_named_property is only called if `name` is in supported_property_names. 116 VERIFY(optional_value.has_value()); 117 118 return optional_value->value; 119} 120 121// https://html.spec.whatwg.org/multipage/dom.html#dom-domstringmap-setitem 122WebIDL::ExceptionOr<void> DOMStringMap::set_value_of_new_named_property(DeprecatedString const& name, JS::Value unconverted_value) 123{ 124 // NOTE: Since LegacyPlatformObject does not know the type of value, we must convert it ourselves. 125 // The type of `value` is `DOMString`. 126 auto value = TRY(unconverted_value.to_deprecated_string(vm())); 127 128 AK::StringBuilder builder; 129 130 // 3. Insert the string data- at the front of name. 131 // NOTE: This is done out of order because StringBuilder doesn't have prepend. 132 builder.append("data-"sv); 133 134 for (size_t character_index = 0; character_index < name.length(); ++character_index) { 135 // 1. If name contains a U+002D HYPHEN-MINUS character (-) followed by an ASCII lower alpha, then throw a "SyntaxError" DOMException. 136 auto current_character = name[character_index]; 137 138 if (current_character == '-' && character_index + 1 < name.length()) { 139 auto next_character = name[character_index + 1]; 140 if (is_ascii_lower_alpha(next_character)) 141 return WebIDL::SyntaxError::create(realm(), "Name cannot contain a '-' followed by a lowercase character."); 142 } 143 144 // 2. For each ASCII upper alpha in name, insert a U+002D HYPHEN-MINUS character (-) before the character and replace the character with the same character converted to ASCII lowercase. 145 if (is_ascii_upper_alpha(current_character)) { 146 builder.append('-'); 147 builder.append(to_ascii_lowercase(current_character)); 148 continue; 149 } 150 151 builder.append(current_character); 152 } 153 154 auto data_name = builder.to_deprecated_string(); 155 156 // FIXME: 4. If name does not match the XML Name production, throw an "InvalidCharacterError" DOMException. 157 158 // 5. Set an attribute value for the DOMStringMap's associated element using name and value. 159 MUST(m_associated_element->set_attribute(data_name, value)); 160 161 return {}; 162} 163 164// https://html.spec.whatwg.org/multipage/dom.html#dom-domstringmap-setitem 165WebIDL::ExceptionOr<void> DOMStringMap::set_value_of_existing_named_property(DeprecatedString const& name, JS::Value value) 166{ 167 return set_value_of_new_named_property(name, value); 168} 169 170// https://html.spec.whatwg.org/multipage/dom.html#dom-domstringmap-removeitem 171WebIDL::ExceptionOr<Bindings::LegacyPlatformObject::DidDeletionFail> DOMStringMap::delete_value(DeprecatedString const& name) 172{ 173 AK::StringBuilder builder; 174 175 // 2. Insert the string data- at the front of name. 176 // NOTE: This is done out of order because StringBuilder doesn't have prepend. 177 builder.append("data-"sv); 178 179 for (auto character : name) { 180 // 1. For each ASCII upper alpha in name, insert a U+002D HYPHEN-MINUS character (-) before the character and replace the character with the same character converted to ASCII lowercase. 181 if (is_ascii_upper_alpha(character)) { 182 builder.append('-'); 183 builder.append(to_ascii_lowercase(character)); 184 continue; 185 } 186 187 builder.append(character); 188 } 189 190 // Remove an attribute by name given name and the DOMStringMap's associated element. 191 auto data_name = builder.to_deprecated_string(); 192 m_associated_element->remove_attribute(data_name); 193 194 // The spec doesn't have the step. This indicates that the deletion was successful. 195 return DidDeletionFail::No; 196} 197 198WebIDL::ExceptionOr<JS::Value> DOMStringMap::named_item_value(DeprecatedFlyString const& name) const 199{ 200 return JS::PrimitiveString::create(vm(), determine_value_of_named_property(name)); 201} 202 203}