summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb/DOM/NamedNodeMap.cpp
blob: 6d47d3c065f6f0309effd23f3fb719a549375545 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
/*
 * Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
 * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
 * Copyright (c) 2022, Alexander Narsudinov <a.narsudinov@gmail.com>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibWeb/DOM/Attr.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(Bindings::cached_web_prototype(element.realm(), "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<DeprecatedString> 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<DeprecatedString> 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 (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
Attr 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].ptr();
}

// https://dom.spec.whatwg.org/#dom-namednodemap-getnameditem
Attr const* NamedNodeMap::get_named_item(StringView qualified_name) const
{
    return get_attribute(qualified_name);
}

// https://dom.spec.whatwg.org/#dom-namednodemap-getnameditemns
Attr const* NamedNodeMap::get_named_item_ns(StringView namespace_, StringView local_name) const
{
    return get_attribute_ns(namespace_, local_name);
}

// https://dom.spec.whatwg.org/#dom-namednodemap-setnameditem
WebIDL::ExceptionOr<Attr const*> NamedNodeMap::set_named_item(Attr& attribute)
{
    return set_attribute(attribute);
}

// https://dom.spec.whatwg.org/#dom-namednodemap-removenameditem
WebIDL::ExceptionOr<Attr 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 WebIDL::NotFoundError::create(realm(), DeprecatedString::formatted("Attribute with name '{}' not found", qualified_name));

    // 3. Return attr.
    return nullptr;
}

// https://dom.spec.whatwg.org/#concept-element-attributes-get-by-name
Attr* NamedNodeMap::get_attribute(StringView qualified_name, size_t* item_index)
{
    return const_cast<Attr*>(const_cast<NamedNodeMap const*>(this)->get_attribute(qualified_name, item_index));
}

// https://dom.spec.whatwg.org/#concept-element-attributes-get-by-name
Attr 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 element’s 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-get-by-namespace
Attr* NamedNodeMap::get_attribute_ns(StringView namespace_, StringView local_name, size_t* item_index)
{
    return const_cast<Attr*>(const_cast<NamedNodeMap const*>(this)->get_attribute_ns(namespace_, local_name, item_index));
}

// https://dom.spec.whatwg.org/#concept-element-attributes-get-by-namespace
Attr const* NamedNodeMap::get_attribute_ns(StringView namespace_, StringView local_name, size_t* item_index) const
{
    if (item_index)
        *item_index = 0;

    // 1. If namespace is the empty string, then set it to null.
    if (namespace_.is_empty())
        namespace_ = {};

    // 2. Return the attribute in element’s attribute list whose namespace is namespace and local name is localName, if any; otherwise null.
    for (auto const& attribute : m_attributes) {
        if (attribute->namespace_uri() == namespace_ && attribute->local_name() == local_name)
            return attribute.ptr();
        if (item_index)
            ++(*item_index);
    }

    return nullptr;
}

// https://dom.spec.whatwg.org/#concept-element-attributes-set
WebIDL::ExceptionOr<Attr const*> NamedNodeMap::set_attribute(Attr& attribute)
{
    // 1. If attr’s element is neither null nor element, throw an "InUseAttributeError" DOMException.
    if ((attribute.owner_element() != nullptr) && (attribute.owner_element() != &associated_element()))
        return WebIDL::InUseAttributeError::create(realm(), "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.
    size_t old_attribute_index = 0;
    auto* old_attribute = get_attribute_ns(attribute.namespace_uri(), 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(Attr& old_attribute, Attr& new_attribute, size_t old_attribute_index)
{
    // 1. Handle attribute changes for oldAttr with oldAttr’s element, oldAttr’s value, and newAttr’s 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 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(Attr& attribute)
{
    // 1. Handle attribute changes for attribute with element, null, and attribute’s value.
    attribute.handle_attribute_changes(associated_element(), {}, attribute.value());

    // 2. Append attribute to element’s attribute list.
    m_attributes.append(attribute);

    // 3. Set attribute’s 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<Attr> attribute = m_attributes.at(attribute_index);

    // 1. Handle attribute changes for attribute with attribute’s element, attribute’s value, and null.
    VERIFY(attribute->owner_element());
    attribute->handle_attribute_changes(*attribute->owner_element(), attribute->value(), {});

    // 2. Remove attribute from attribute’s element’s attribute list.
    m_attributes.remove(attribute_index);

    // 3. Set attribute’s element to null.
    attribute->set_owner_element(nullptr);
}

// https://dom.spec.whatwg.org/#concept-element-attributes-remove-by-name
Attr 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;
}

}