Coverage Report

Created: 2025-11-02 07:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/DOM/Node.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
4
 * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
5
 *
6
 * SPDX-License-Identifier: BSD-2-Clause
7
 */
8
9
#include <AK/HashTable.h>
10
#include <AK/IDAllocator.h>
11
#include <AK/StringBuilder.h>
12
#include <LibJS/Heap/DeferGC.h>
13
#include <LibJS/Runtime/FunctionObject.h>
14
#include <LibRegex/Regex.h>
15
#include <LibURL/Origin.h>
16
#include <LibWeb/Bindings/MainThreadVM.h>
17
#include <LibWeb/Bindings/NodePrototype.h>
18
#include <LibWeb/DOM/Attr.h>
19
#include <LibWeb/DOM/CDATASection.h>
20
#include <LibWeb/DOM/Comment.h>
21
#include <LibWeb/DOM/DocumentType.h>
22
#include <LibWeb/DOM/Element.h>
23
#include <LibWeb/DOM/ElementFactory.h>
24
#include <LibWeb/DOM/Event.h>
25
#include <LibWeb/DOM/EventDispatcher.h>
26
#include <LibWeb/DOM/IDLEventListener.h>
27
#include <LibWeb/DOM/LiveNodeList.h>
28
#include <LibWeb/DOM/MutationType.h>
29
#include <LibWeb/DOM/NamedNodeMap.h>
30
#include <LibWeb/DOM/Node.h>
31
#include <LibWeb/DOM/NodeIterator.h>
32
#include <LibWeb/DOM/ProcessingInstruction.h>
33
#include <LibWeb/DOM/Range.h>
34
#include <LibWeb/DOM/ShadowRoot.h>
35
#include <LibWeb/DOM/StaticNodeList.h>
36
#include <LibWeb/HTML/CustomElements/CustomElementReactionNames.h>
37
#include <LibWeb/HTML/HTMLAnchorElement.h>
38
#include <LibWeb/HTML/HTMLSlotElement.h>
39
#include <LibWeb/HTML/HTMLStyleElement.h>
40
#include <LibWeb/HTML/Navigable.h>
41
#include <LibWeb/HTML/NavigableContainer.h>
42
#include <LibWeb/HTML/Parser/HTMLParser.h>
43
#include <LibWeb/Infra/CharacterTypes.h>
44
#include <LibWeb/Layout/Node.h>
45
#include <LibWeb/Layout/TextNode.h>
46
#include <LibWeb/Layout/Viewport.h>
47
#include <LibWeb/Namespace.h>
48
#include <LibWeb/Painting/Paintable.h>
49
#include <LibWeb/Painting/PaintableBox.h>
50
51
namespace Web::DOM {
52
53
static IDAllocator s_unique_id_allocator;
54
static HashMap<i32, Node*> s_node_directory;
55
56
static i32 allocate_unique_id(Node* node)
57
0
{
58
0
    i32 id = s_unique_id_allocator.allocate();
59
0
    s_node_directory.set(id, node);
60
0
    return id;
61
0
}
62
63
static void deallocate_unique_id(i32 node_id)
64
0
{
65
0
    if (!s_node_directory.remove(node_id))
66
0
        VERIFY_NOT_REACHED();
67
0
    s_unique_id_allocator.deallocate(node_id);
68
0
}
69
70
Node* Node::from_unique_id(i32 unique_id)
71
0
{
72
0
    return s_node_directory.get(unique_id).value_or(nullptr);
73
0
}
74
75
Node::Node(JS::Realm& realm, Document& document, NodeType type)
76
0
    : EventTarget(realm)
77
0
    , m_document(&document)
78
0
    , m_type(type)
79
0
    , m_unique_id(allocate_unique_id(this))
80
0
{
81
0
}
82
83
Node::Node(Document& document, NodeType type)
84
0
    : Node(document.realm(), document, type)
85
0
{
86
0
}
87
88
0
Node::~Node() = default;
89
90
void Node::finalize()
91
0
{
92
0
    Base::finalize();
93
0
    deallocate_unique_id(m_unique_id);
94
0
}
95
96
void Node::visit_edges(Cell::Visitor& visitor)
97
0
{
98
0
    Base::visit_edges(visitor);
99
0
    visitor.visit(m_document);
100
0
    visitor.visit(m_parent);
101
0
    visitor.visit(m_first_child);
102
0
    visitor.visit(m_last_child);
103
0
    visitor.visit(m_next_sibling);
104
0
    visitor.visit(m_previous_sibling);
105
0
    visitor.visit(m_child_nodes);
106
107
0
    visitor.visit(m_layout_node);
108
0
    visitor.visit(m_paintable);
109
110
0
    if (m_registered_observer_list) {
111
0
        visitor.visit(*m_registered_observer_list);
112
0
    }
113
0
}
114
115
// https://dom.spec.whatwg.org/#dom-node-baseuri
116
String Node::base_uri() const
117
0
{
118
    // Return this’s node document’s document base URL, serialized.
119
0
    return MUST(document().base_url().to_string());
120
0
}
121
122
const HTML::HTMLAnchorElement* Node::enclosing_link_element() const
123
0
{
124
0
    for (auto* node = this; node; node = node->parent()) {
125
0
        if (!is<HTML::HTMLAnchorElement>(*node))
126
0
            continue;
127
0
        auto const& anchor_element = static_cast<HTML::HTMLAnchorElement const&>(*node);
128
0
        if (anchor_element.has_attribute(HTML::AttributeNames::href))
129
0
            return &anchor_element;
130
0
    }
131
0
    return nullptr;
132
0
}
133
134
const HTML::HTMLElement* Node::enclosing_html_element() const
135
0
{
136
0
    return first_ancestor_of_type<HTML::HTMLElement>();
137
0
}
138
139
const HTML::HTMLElement* Node::enclosing_html_element_with_attribute(FlyString const& attribute) const
140
0
{
141
0
    for (auto* node = this; node; node = node->parent()) {
142
0
        if (is<HTML::HTMLElement>(*node) && verify_cast<HTML::HTMLElement>(*node).has_attribute(attribute))
143
0
            return verify_cast<HTML::HTMLElement>(node);
144
0
    }
145
0
    return nullptr;
146
0
}
147
148
// https://dom.spec.whatwg.org/#concept-descendant-text-content
149
String Node::descendant_text_content() const
150
0
{
151
0
    StringBuilder builder;
152
0
    for_each_in_subtree_of_type<Text>([&](auto& text_node) {
153
0
        builder.append(text_node.data());
154
0
        return TraversalDecision::Continue;
155
0
    });
156
0
    return builder.to_string_without_validation();
157
0
}
158
159
// https://dom.spec.whatwg.org/#dom-node-textcontent
160
Optional<String> Node::text_content() const
161
0
{
162
    // The textContent getter steps are to return the following, switching on the interface this implements:
163
164
    // If DocumentFragment or Element, return the descendant text content of this.
165
0
    if (is<DocumentFragment>(this) || is<Element>(this))
166
0
        return descendant_text_content();
167
168
    // If CharacterData, return this’s data.
169
0
    if (is<CharacterData>(this))
170
0
        return static_cast<CharacterData const&>(*this).data();
171
172
    // If Attr node, return this's value.
173
0
    if (is<Attr>(*this))
174
0
        return static_cast<Attr const&>(*this).value();
175
176
    // Otherwise, return null
177
0
    return {};
178
0
}
179
180
// https://dom.spec.whatwg.org/#ref-for-dom-node-textcontent%E2%91%A0
181
void Node::set_text_content(Optional<String> const& maybe_content)
182
0
{
183
    // The textContent setter steps are to, if the given value is null, act as if it was the empty string instead,
184
    // and then do as described below, switching on the interface this implements:
185
0
    auto content = maybe_content.value_or(String {});
186
187
    // If DocumentFragment or Element, string replace all with the given value within this.
188
0
    if (is<DocumentFragment>(this) || is<Element>(this)) {
189
0
        string_replace_all(content);
190
0
    }
191
192
    // If CharacterData, replace data with node this, offset 0, count this’s length, and data the given value.
193
0
    else if (is<CharacterData>(this)) {
194
195
0
        auto* character_data_node = verify_cast<CharacterData>(this);
196
0
        character_data_node->set_data(content);
197
198
        // FIXME: CharacterData::set_data is not spec compliant. Make this match the spec when set_data becomes spec compliant.
199
        //        Do note that this will make this function able to throw an exception.
200
0
    }
201
202
    // If Attr, set an existing attribute value with this and the given value.
203
0
    if (is<Attr>(*this)) {
204
0
        static_cast<Attr&>(*this).set_value(content);
205
0
    }
206
207
    // Otherwise, do nothing.
208
209
0
    if (is_connected()) {
210
        // FIXME: If there are any :has() selectors, we currently invalidate style for the whole document.
211
        //        We need to find a way to invalidate less!
212
0
        if (document().style_computer().has_has_selectors()) {
213
0
            document().invalidate_style(StyleInvalidationReason::NodeSetTextContent);
214
0
        } else {
215
0
            invalidate_style(StyleInvalidationReason::NodeSetTextContent);
216
0
        }
217
0
        document().invalidate_layout_tree();
218
0
    }
219
220
0
    document().bump_dom_tree_version();
221
0
}
222
223
// https://dom.spec.whatwg.org/#dom-node-normalize
224
WebIDL::ExceptionOr<void> Node::normalize()
225
0
{
226
0
    auto contiguous_exclusive_text_nodes_excluding_self = [](Node& node) {
227
        // https://dom.spec.whatwg.org/#contiguous-exclusive-text-nodes
228
        // The contiguous exclusive Text nodes of a node node are node, node’s previous sibling exclusive Text node, if any,
229
        // and its contiguous exclusive Text nodes, and node’s next sibling exclusive Text node, if any,
230
        // and its contiguous exclusive Text nodes, avoiding any duplicates.
231
        // NOTE: The callers of this method require node itself to be excluded.
232
0
        Vector<Text*> nodes;
233
234
0
        auto* current_node = node.previous_sibling();
235
0
        while (current_node) {
236
0
            if (!current_node->is_text())
237
0
                break;
238
239
0
            nodes.append(static_cast<Text*>(current_node));
240
0
            current_node = current_node->previous_sibling();
241
0
        }
242
243
        // Reverse the order of the nodes so that they are in tree order.
244
0
        nodes.reverse();
245
246
0
        current_node = node.next_sibling();
247
0
        while (current_node) {
248
0
            if (!current_node->is_text())
249
0
                break;
250
251
0
            nodes.append(static_cast<Text*>(current_node));
252
0
            current_node = current_node->next_sibling();
253
0
        }
254
255
0
        return nodes;
256
0
    };
257
258
    // The normalize() method steps are to run these steps for each descendant exclusive Text node node of this
259
0
    Vector<Text&> descendant_exclusive_text_nodes;
260
0
    for_each_in_inclusive_subtree_of_type<Text>([&](Text const& node) {
261
0
        if (!node.is_cdata_section())
262
0
            descendant_exclusive_text_nodes.append(const_cast<Text&>(node));
263
264
0
        return TraversalDecision::Continue;
265
0
    });
266
267
0
    for (auto& node : descendant_exclusive_text_nodes) {
268
        // 1. Let length be node’s length.
269
0
        auto& character_data = static_cast<CharacterData&>(node);
270
0
        auto length = character_data.length_in_utf16_code_units();
271
272
        // 2. If length is zero, then remove node and continue with the next exclusive Text node, if any.
273
0
        if (length == 0) {
274
0
            if (node.parent())
275
0
                node.remove();
276
0
            continue;
277
0
        }
278
279
        // 3. Let data be the concatenation of the data of node’s contiguous exclusive Text nodes (excluding itself), in tree order.
280
0
        StringBuilder data;
281
0
        for (auto const& text_node : contiguous_exclusive_text_nodes_excluding_self(node))
282
0
            data.append(text_node->data());
283
284
        // 4. Replace data with node node, offset length, count 0, and data data.
285
0
        TRY(character_data.replace_data(length, 0, MUST(data.to_string())));
286
287
        // 5. Let currentNode be node’s next sibling.
288
0
        auto* current_node = node.next_sibling();
289
290
        // 6. While currentNode is an exclusive Text node:
291
0
        while (current_node && is<Text>(*current_node)) {
292
            // 1. For each live range whose start node is currentNode, add length to its start offset and set its start node to node.
293
0
            for (auto& range : Range::live_ranges()) {
294
0
                if (range->start_container() == current_node)
295
0
                    TRY(range->set_start(node, range->start_offset() + length));
296
0
            }
297
298
            // 2. For each live range whose end node is currentNode, add length to its end offset and set its end node to node.
299
0
            for (auto& range : Range::live_ranges()) {
300
0
                if (range->end_container() == current_node)
301
0
                    TRY(range->set_end(node, range->end_offset() + length));
302
0
            }
303
304
            // 3. For each live range whose start node is currentNode’s parent and start offset is currentNode’s index, set its start node to node and its start offset to length.
305
0
            for (auto& range : Range::live_ranges()) {
306
0
                if (range->start_container() == current_node->parent() && range->start_offset() == current_node->index())
307
0
                    TRY(range->set_start(node, length));
308
0
            }
309
310
            // 4. For each live range whose end node is currentNode’s parent and end offset is currentNode’s index, set its end node to node and its end offset to length.
311
0
            for (auto& range : Range::live_ranges()) {
312
0
                if (range->end_container() == current_node->parent() && range->end_offset() == current_node->index())
313
0
                    TRY(range->set_end(node, length));
314
0
            }
315
316
            // 5. Add currentNode’s length to length.
317
0
            length += static_cast<Text&>(*current_node).length();
318
319
            // 6. Set currentNode to its next sibling.
320
0
            current_node = current_node->next_sibling();
321
0
        }
322
323
        // 7. Remove node’s contiguous exclusive Text nodes (excluding itself), in tree order.
324
0
        for (auto const& text_node : contiguous_exclusive_text_nodes_excluding_self(node))
325
0
            text_node->remove();
326
0
    }
327
328
0
    return {};
329
0
}
330
331
// https://dom.spec.whatwg.org/#dom-node-nodevalue
332
Optional<String> Node::node_value() const
333
0
{
334
    // The nodeValue getter steps are to return the following, switching on the interface this implements:
335
336
    // If Attr, return this’s value.
337
0
    if (is<Attr>(this)) {
338
0
        return verify_cast<Attr>(this)->value();
339
0
    }
340
341
    // If CharacterData, return this’s data.
342
0
    if (is<CharacterData>(this)) {
343
0
        return verify_cast<CharacterData>(this)->data();
344
0
    }
345
346
    // Otherwise, return null.
347
0
    return {};
348
0
}
349
350
// https://dom.spec.whatwg.org/#ref-for-dom-node-nodevalue%E2%91%A0
351
void Node::set_node_value(Optional<String> const& maybe_value)
352
0
{
353
    // The nodeValue setter steps are to, if the given value is null, act as if it was the empty string instead,
354
    // and then do as described below, switching on the interface this implements:
355
0
    auto value = maybe_value.value_or(String {});
356
357
    // If Attr, set an existing attribute value with this and the given value.
358
0
    if (is<Attr>(this)) {
359
0
        verify_cast<Attr>(this)->set_value(move(value));
360
0
    } else if (is<CharacterData>(this)) {
361
        // If CharacterData, replace data with node this, offset 0, count this’s length, and data the given value.
362
0
        verify_cast<CharacterData>(this)->set_data(value);
363
0
    }
364
365
    // Otherwise, do nothing.
366
0
}
367
368
// https://html.spec.whatwg.org/multipage/document-sequences.html#node-navigable
369
JS::GCPtr<HTML::Navigable> Node::navigable() const
370
0
{
371
0
    auto& document = const_cast<Document&>(this->document());
372
0
    if (auto cached_navigable = document.cached_navigable()) {
373
0
        if (cached_navigable->active_document() == &document)
374
0
            return cached_navigable;
375
0
    }
376
377
    // To get the node navigable of a node node, return the navigable whose active document is node's node document,
378
    // or null if there is no such navigable.
379
0
    auto navigable = HTML::Navigable::navigable_with_active_document(document);
380
0
    document.set_cached_navigable(navigable);
381
0
    return navigable;
382
0
}
383
384
[[maybe_unused]] static StringView to_string(StyleInvalidationReason reason)
385
0
{
386
0
#define __ENUMERATE_STYLE_INVALIDATION_REASON(reason) \
387
0
    case StyleInvalidationReason::reason:             \
388
0
        return #reason##sv;
389
0
    switch (reason) {
390
0
        ENUMERATE_STYLE_INVALIDATION_REASONS(__ENUMERATE_STYLE_INVALIDATION_REASON)
391
0
    default:
392
0
        VERIFY_NOT_REACHED();
393
0
    }
394
0
}
395
396
void Node::invalidate_style(StyleInvalidationReason reason)
397
0
{
398
0
    if (is_character_data())
399
0
        return;
400
401
0
    if (!needs_style_update() && !document().needs_full_style_update()) {
402
0
        dbgln_if(STYLE_INVALIDATION_DEBUG, "Invalidate style ({}): {}", to_string(reason), debug_description());
403
0
    }
404
405
0
    if (is_document()) {
406
0
        auto& document = static_cast<DOM::Document&>(*this);
407
0
        document.set_needs_full_style_update(true);
408
0
        document.schedule_style_update();
409
0
        return;
410
0
    }
411
412
    // If the document is already marked for a full style update, there's no need to do anything here.
413
0
    if (document().needs_full_style_update()) {
414
0
        return;
415
0
    }
416
417
    // When invalidating style for a node, we actually invalidate:
418
    // - the node itself
419
    // - all of its descendants
420
    // - all of its preceding siblings and their descendants (only on DOM insert/remove)
421
    // - all of its subsequent siblings and their descendants
422
    // FIXME: This is a lot of invalidation and we should implement more sophisticated invalidation to do less work!
423
424
0
    auto invalidate_entire_subtree = [&](Node& subtree_root) {
425
0
        subtree_root.for_each_in_inclusive_subtree([&](Node& node) {
426
0
            node.m_needs_style_update = true;
427
0
            if (node.has_children())
428
0
                node.m_child_needs_style_update = true;
429
0
            if (auto shadow_root = node.is_element() ? static_cast<DOM::Element&>(node).shadow_root() : nullptr) {
430
0
                node.m_child_needs_style_update = true;
431
0
                shadow_root->m_needs_style_update = true;
432
0
                if (shadow_root->has_children())
433
0
                    shadow_root->m_child_needs_style_update = true;
434
0
            }
435
0
            return TraversalDecision::Continue;
436
0
        });
437
0
    };
438
439
0
    invalidate_entire_subtree(*this);
440
441
0
    if (reason == StyleInvalidationReason::NodeInsertBefore || reason == StyleInvalidationReason::NodeRemove) {
442
0
        for (auto* sibling = previous_sibling(); sibling; sibling = sibling->previous_sibling()) {
443
0
            if (sibling->is_element())
444
0
                invalidate_entire_subtree(*sibling);
445
0
        }
446
0
    }
447
448
0
    for (auto* sibling = next_sibling(); sibling; sibling = sibling->next_sibling()) {
449
0
        if (sibling->is_element())
450
0
            invalidate_entire_subtree(*sibling);
451
0
    }
452
453
0
    for (auto* ancestor = parent_or_shadow_host(); ancestor; ancestor = ancestor->parent_or_shadow_host())
454
0
        ancestor->m_child_needs_style_update = true;
455
0
    document().schedule_style_update();
456
0
}
457
458
String Node::child_text_content() const
459
0
{
460
0
    if (!is<ParentNode>(*this))
461
0
        return String {};
462
463
0
    StringBuilder builder;
464
0
    verify_cast<ParentNode>(*this).for_each_child([&](auto& child) {
465
0
        if (is<Text>(child)) {
466
0
            auto maybe_content = verify_cast<Text>(child).text_content();
467
0
            if (maybe_content.has_value())
468
0
                builder.append(maybe_content.value());
469
0
        }
470
0
        return IterationDecision::Continue;
471
0
    });
472
0
    return MUST(builder.to_string());
473
0
}
474
475
// https://dom.spec.whatwg.org/#concept-tree-root
476
Node& Node::root()
477
0
{
478
    // The root of an object is itself, if its parent is null, or else it is the root of its parent.
479
    // The root of a tree is any object participating in that tree whose parent is null.
480
0
    Node* root = this;
481
0
    while (root->parent())
482
0
        root = root->parent();
483
0
    return *root;
484
0
}
485
486
// https://dom.spec.whatwg.org/#concept-shadow-including-root
487
Node& Node::shadow_including_root()
488
0
{
489
    // The shadow-including root of an object is its root’s host’s shadow-including root,
490
    // if the object’s root is a shadow root; otherwise its root.
491
0
    auto& node_root = root();
492
0
    if (is<ShadowRoot>(node_root)) {
493
0
        if (auto* host = static_cast<ShadowRoot&>(node_root).host(); host)
494
0
            return host->shadow_including_root();
495
0
    }
496
0
    return node_root;
497
0
}
498
499
// https://dom.spec.whatwg.org/#connected
500
bool Node::is_connected() const
501
0
{
502
    // An element is connected if its shadow-including root is a document.
503
0
    return shadow_including_root().is_document();
504
0
}
505
506
// https://html.spec.whatwg.org/multipage/infrastructure.html#browsing-context-connected
507
bool Node::is_browsing_context_connected() const
508
0
{
509
    // A node is browsing-context connected when it is connected and its shadow-including root's browsing context is non-null.
510
0
    return is_connected() && shadow_including_root().document().browsing_context();
511
0
}
512
513
// https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
514
WebIDL::ExceptionOr<void> Node::ensure_pre_insertion_validity(JS::NonnullGCPtr<Node> node, JS::GCPtr<Node> child) const
515
0
{
516
    // 1. If parent is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException.
517
0
    if (!is<Document>(this) && !is<DocumentFragment>(this) && !is<Element>(this))
518
0
        return WebIDL::HierarchyRequestError::create(realm(), "Can only insert into a document, document fragment or element"_string);
519
520
    // 2. If node is a host-including inclusive ancestor of parent, then throw a "HierarchyRequestError" DOMException.
521
0
    if (node->is_host_including_inclusive_ancestor_of(*this))
522
0
        return WebIDL::HierarchyRequestError::create(realm(), "New node is an ancestor of this node"_string);
523
524
    // 3. If child is non-null and its parent is not parent, then throw a "NotFoundError" DOMException.
525
0
    if (child && child->parent() != this)
526
0
        return WebIDL::NotFoundError::create(realm(), "This node is not the parent of the given child"_string);
527
528
    // FIXME: All the following "Invalid node type for insertion" messages could be more descriptive.
529
    // 4. If node is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException.
530
0
    if (!is<DocumentFragment>(*node) && !is<DocumentType>(*node) && !is<Element>(*node) && !is<Text>(*node) && !is<Comment>(*node) && !is<ProcessingInstruction>(*node) && !is<CDATASection>(*node))
531
0
        return WebIDL::HierarchyRequestError::create(realm(), "Invalid node type for insertion"_string);
532
533
    // 5. If either node is a Text node and parent is a document, or node is a doctype and parent is not a document, then throw a "HierarchyRequestError" DOMException.
534
0
    if ((is<Text>(*node) && is<Document>(this)) || (is<DocumentType>(*node) && !is<Document>(this)))
535
0
        return WebIDL::HierarchyRequestError::create(realm(), "Invalid node type for insertion"_string);
536
537
    // 6. If parent is a document, and any of the statements below, switched on the interface node implements, are true, then throw a "HierarchyRequestError" DOMException.
538
0
    if (is<Document>(this)) {
539
        // DocumentFragment
540
0
        if (is<DocumentFragment>(*node)) {
541
            // If node has more than one element child or has a Text node child.
542
            // Otherwise, if node has one element child and either parent has an element child, child is a doctype, or child is non-null and a doctype is following child.
543
0
            auto node_element_child_count = verify_cast<DocumentFragment>(*node).child_element_count();
544
0
            if ((node_element_child_count > 1 || node->has_child_of_type<Text>())
545
0
                || (node_element_child_count == 1 && (has_child_of_type<Element>() || is<DocumentType>(child.ptr()) || (child && child->has_following_node_of_type_in_tree_order<DocumentType>())))) {
546
0
                return WebIDL::HierarchyRequestError::create(realm(), "Invalid node type for insertion"_string);
547
0
            }
548
0
        } else if (is<Element>(*node)) {
549
            // Element
550
            // If parent has an element child, child is a doctype, or child is non-null and a doctype is following child.
551
0
            if (has_child_of_type<Element>() || is<DocumentType>(child.ptr()) || (child && child->has_following_node_of_type_in_tree_order<DocumentType>()))
552
0
                return WebIDL::HierarchyRequestError::create(realm(), "Invalid node type for insertion"_string);
553
0
        } else if (is<DocumentType>(*node)) {
554
            // DocumentType
555
            // parent has a doctype child, child is non-null and an element is preceding child, or child is null and parent has an element child.
556
0
            if (has_child_of_type<DocumentType>() || (child && child->has_preceding_node_of_type_in_tree_order<Element>()) || (!child && has_child_of_type<Element>()))
557
0
                return WebIDL::HierarchyRequestError::create(realm(), "Invalid node type for insertion"_string);
558
0
        }
559
0
    }
560
561
0
    return {};
562
0
}
563
564
// https://dom.spec.whatwg.org/#concept-node-insert
565
void Node::insert_before(JS::NonnullGCPtr<Node> node, JS::GCPtr<Node> child, bool suppress_observers)
566
0
{
567
    // 1. Let nodes be node’s children, if node is a DocumentFragment node; otherwise « node ».
568
0
    Vector<JS::Handle<Node>> nodes;
569
0
    if (is<DocumentFragment>(*node))
570
0
        nodes = node->children_as_vector();
571
0
    else
572
0
        nodes.append(JS::make_handle(*node));
573
574
    // 2. Let count be nodes’s size.
575
0
    auto count = nodes.size();
576
577
    // 3. If count is 0, then return.
578
0
    if (count == 0)
579
0
        return;
580
581
    // 4. If node is a DocumentFragment node, then:
582
0
    if (is<DocumentFragment>(*node)) {
583
        // 1. Remove its children with the suppress observers flag set.
584
0
        node->remove_all_children(true);
585
586
        // 2. Queue a tree mutation record for node with « », nodes, null, and null.
587
        // NOTE: This step intentionally does not pay attention to the suppress observers flag.
588
0
        node->queue_tree_mutation_record({}, nodes, nullptr, nullptr);
589
0
    }
590
591
    // 5. If child is non-null, then:
592
0
    if (child) {
593
        // 1. For each live range whose start node is parent and start offset is greater than child’s index, increase its start offset by count.
594
0
        for (auto& range : Range::live_ranges()) {
595
0
            if (range->start_container() == this && range->start_offset() > child->index())
596
0
                range->increase_start_offset({}, count);
597
0
        }
598
599
        // 2. For each live range whose end node is parent and end offset is greater than child’s index, increase its end offset by count.
600
0
        for (auto& range : Range::live_ranges()) {
601
0
            if (range->end_container() == this && range->end_offset() > child->index())
602
0
                range->increase_end_offset({}, count);
603
0
        }
604
0
    }
605
606
    // 6. Let previousSibling be child’s previous sibling or parent’s last child if child is null.
607
0
    JS::GCPtr<Node> previous_sibling;
608
0
    if (child)
609
0
        previous_sibling = child->previous_sibling();
610
0
    else
611
0
        previous_sibling = last_child();
612
613
    // 7. For each node in nodes, in tree order:
614
    // FIXME: In tree order
615
0
    for (auto& node_to_insert : nodes) {
616
        // 1. Adopt node into parent’s node document.
617
0
        document().adopt_node(*node_to_insert);
618
619
        // 2. If child is null, then append node to parent’s children.
620
0
        if (!child)
621
0
            append_child_impl(*node_to_insert);
622
        // 3. Otherwise, insert node into parent’s children before child’s index.
623
0
        else
624
0
            insert_before_impl(*node_to_insert, child);
625
626
        // 4. If parent is a shadow host whose shadow root’s slot assignment is "named" and node is a slottable, then
627
        //    assign a slot for node.
628
0
        if (is_element()) {
629
0
            auto& element = static_cast<DOM::Element&>(*this);
630
631
0
            auto is_named_shadow_host = element.is_shadow_host()
632
0
                && element.shadow_root()->slot_assignment() == Bindings::SlotAssignmentMode::Named;
633
634
0
            if (is_named_shadow_host && node_to_insert->is_slottable())
635
0
                assign_a_slot(node_to_insert->as_slottable());
636
0
        }
637
638
        // 5. If parent’s root is a shadow root, and parent is a slot whose assigned nodes is the empty list, then run
639
        //    signal a slot change for parent.
640
0
        if (root().is_shadow_root() && is<HTML::HTMLSlotElement>(*this)) {
641
0
            auto& slot = static_cast<HTML::HTMLSlotElement&>(*this);
642
643
0
            if (slot.assigned_nodes_internal().is_empty())
644
0
                signal_a_slot_change(slot);
645
0
        }
646
647
        // 6. Run assign slottables for a tree with node’s root.
648
0
        assign_slottables_for_a_tree(node_to_insert->root());
649
650
0
        node_to_insert->invalidate_style(StyleInvalidationReason::NodeInsertBefore);
651
652
        // 7. For each shadow-including inclusive descendant inclusiveDescendant of node, in shadow-including tree order:
653
0
        node_to_insert->for_each_shadow_including_inclusive_descendant([&](Node& inclusive_descendant) {
654
            // 1. Run the insertion steps with inclusiveDescendant.
655
0
            inclusive_descendant.inserted();
656
657
            // 2. If inclusiveDescendant is connected, then:
658
            // NOTE: This is not specified here in the spec, but these steps can only be performed on an element.
659
0
            if (inclusive_descendant.is_connected() && is<DOM::Element>(inclusive_descendant)) {
660
0
                auto& element = static_cast<DOM::Element&>(inclusive_descendant);
661
662
                // 1. If inclusiveDescendant is custom, then enqueue a custom element callback reaction with inclusiveDescendant,
663
                //    callback name "connectedCallback", and an empty argument list.
664
0
                if (element.is_custom()) {
665
0
                    JS::MarkedVector<JS::Value> empty_arguments { vm().heap() };
666
0
                    element.enqueue_a_custom_element_callback_reaction(HTML::CustomElementReactionNames::connectedCallback, move(empty_arguments));
667
0
                }
668
669
                // 2. Otherwise, try to upgrade inclusiveDescendant.
670
                // NOTE: If this successfully upgrades inclusiveDescendant, its connectedCallback will be enqueued automatically during
671
                //       the upgrade an element algorithm.
672
0
                else {
673
0
                    element.try_to_upgrade();
674
0
                }
675
0
            }
676
677
0
            return TraversalDecision::Continue;
678
0
        });
679
0
    }
680
681
    // 8. If suppress observers flag is unset, then queue a tree mutation record for parent with nodes, « », previousSibling, and child.
682
0
    if (!suppress_observers) {
683
0
        queue_tree_mutation_record(move(nodes), {}, previous_sibling.ptr(), child.ptr());
684
0
    }
685
686
    // 9. Run the children changed steps for parent.
687
0
    children_changed();
688
689
0
    if (is_connected()) {
690
        // FIXME: This will need to become smarter when we implement the :has() selector.
691
0
        invalidate_style(StyleInvalidationReason::ParentOfInsertedNode);
692
0
        document().invalidate_layout_tree();
693
0
    }
694
695
0
    document().bump_dom_tree_version();
696
0
}
697
698
// https://dom.spec.whatwg.org/#concept-node-pre-insert
699
WebIDL::ExceptionOr<JS::NonnullGCPtr<Node>> Node::pre_insert(JS::NonnullGCPtr<Node> node, JS::GCPtr<Node> child)
700
0
{
701
    // 1. Ensure pre-insertion validity of node into parent before child.
702
0
    TRY(ensure_pre_insertion_validity(node, child));
703
704
    // 2. Let referenceChild be child.
705
0
    auto reference_child = child;
706
707
    // 3. If referenceChild is node, then set referenceChild to node’s next sibling.
708
0
    if (reference_child == node)
709
0
        reference_child = node->next_sibling();
710
711
    // 4. Insert node into parent before referenceChild.
712
0
    insert_before(node, reference_child);
713
714
    // 5. Return node.
715
0
    return node;
716
0
}
717
718
// https://dom.spec.whatwg.org/#dom-node-removechild
719
WebIDL::ExceptionOr<JS::NonnullGCPtr<Node>> Node::remove_child(JS::NonnullGCPtr<Node> child)
720
0
{
721
    // The removeChild(child) method steps are to return the result of pre-removing child from this.
722
0
    return pre_remove(child);
723
0
}
724
725
// https://dom.spec.whatwg.org/#concept-node-pre-remove
726
WebIDL::ExceptionOr<JS::NonnullGCPtr<Node>> Node::pre_remove(JS::NonnullGCPtr<Node> child)
727
0
{
728
    // 1. If child’s parent is not parent, then throw a "NotFoundError" DOMException.
729
0
    if (child->parent() != this)
730
0
        return WebIDL::NotFoundError::create(realm(), "Child does not belong to this node"_string);
731
732
    // 2. Remove child.
733
0
    child->remove();
734
735
    // 3. Return child.
736
0
    return child;
737
0
}
738
739
// https://dom.spec.whatwg.org/#concept-node-append
740
WebIDL::ExceptionOr<JS::NonnullGCPtr<Node>> Node::append_child(JS::NonnullGCPtr<Node> node)
741
0
{
742
    // To append a node to a parent, pre-insert node into parent before null.
743
0
    return pre_insert(node, nullptr);
744
0
}
745
746
// https://dom.spec.whatwg.org/#concept-node-remove
747
void Node::remove(bool suppress_observers)
748
0
{
749
0
    bool was_connected = is_connected();
750
0
    bool had_layout_node = layout_node();
751
752
    // 1. Let parent be node’s parent
753
0
    auto* parent = this->parent();
754
755
    // 2. Assert: parent is non-null.
756
0
    VERIFY(parent);
757
758
    // 3. Let index be node’s index.
759
0
    auto index = this->index();
760
761
    // 4. For each live range whose start node is an inclusive descendant of node, set its start to (parent, index).
762
0
    for (auto& range : Range::live_ranges()) {
763
0
        if (range->start_container()->is_inclusive_descendant_of(*this))
764
0
            MUST(range->set_start(*parent, index));
765
0
    }
766
767
    // 5. For each live range whose end node is an inclusive descendant of node, set its end to (parent, index).
768
0
    for (auto& range : Range::live_ranges()) {
769
0
        if (range->end_container()->is_inclusive_descendant_of(*this))
770
0
            MUST(range->set_end(*parent, index));
771
0
    }
772
773
    // 6. For each live range whose start node is parent and start offset is greater than index, decrease its start offset by 1.
774
0
    for (auto& range : Range::live_ranges()) {
775
0
        if (range->start_container() == parent && range->start_offset() > index)
776
0
            range->decrease_start_offset({}, 1);
777
0
    }
778
779
    // 7. For each live range whose end node is parent and end offset is greater than index, decrease its end offset by 1.
780
0
    for (auto& range : Range::live_ranges()) {
781
0
        if (range->end_container() == parent && range->end_offset() > index)
782
0
            range->decrease_end_offset({}, 1);
783
0
    }
784
785
    // 8. For each NodeIterator object iterator whose root’s node document is node’s node document, run the NodeIterator pre-removing steps given node and iterator.
786
0
    document().for_each_node_iterator([&](NodeIterator& node_iterator) {
787
0
        node_iterator.run_pre_removing_steps(*this);
788
0
    });
789
790
    // 9. Let oldPreviousSibling be node’s previous sibling.
791
0
    JS::GCPtr<Node> old_previous_sibling = previous_sibling();
792
793
    // 10. Let oldNextSibling be node’s next sibling.
794
0
    JS::GCPtr<Node> old_next_sibling = next_sibling();
795
796
    // 11. Remove node from its parent’s children.
797
0
    parent->remove_child_impl(*this);
798
799
    // 12. If node is assigned, then run assign slottables for node’s assigned slot.
800
0
    if (auto assigned_slot = assigned_slot_for_node(*this))
801
0
        assign_slottables(*assigned_slot);
802
803
    // 13. If parent’s root is a shadow root, and parent is a slot whose assigned nodes is the empty list, then run
804
    //     signal a slot change for parent.
805
0
    if (parent->root().is_shadow_root() && is<HTML::HTMLSlotElement>(parent)) {
806
0
        auto& slot = static_cast<HTML::HTMLSlotElement&>(*parent);
807
808
0
        if (slot.assigned_nodes_internal().is_empty())
809
0
            signal_a_slot_change(slot);
810
0
    }
811
812
    // 14. If node has an inclusive descendant that is a slot, then:
813
0
    auto has_descendent_slot = false;
814
815
0
    for_each_in_inclusive_subtree_of_type<HTML::HTMLSlotElement>([&](auto const&) {
816
0
        has_descendent_slot = true;
817
0
        return TraversalDecision::Break;
818
0
    });
819
820
0
    if (has_descendent_slot) {
821
        // 1. Run assign slottables for a tree with parent’s root.
822
0
        assign_slottables_for_a_tree(parent->root());
823
824
        // 2. Run assign slottables for a tree with node.
825
0
        assign_slottables_for_a_tree(*this);
826
0
    }
827
828
    // 15. Run the removing steps with node and parent.
829
0
    removed_from(parent);
830
831
    // 16. Let isParentConnected be parent’s connected.
832
0
    bool is_parent_connected = parent->is_connected();
833
834
    // 17. If node is custom and isParentConnected is true, then enqueue a custom element callback reaction with node,
835
    //     callback name "disconnectedCallback", and an empty argument list.
836
    // Spec Note: It is intentional for now that custom elements do not get parent passed.
837
    //            This might change in the future if there is a need.
838
0
    if (is<DOM::Element>(*this)) {
839
0
        auto& element = static_cast<DOM::Element&>(*this);
840
841
0
        if (element.is_custom() && is_parent_connected) {
842
0
            JS::MarkedVector<JS::Value> empty_arguments { vm().heap() };
843
0
            element.enqueue_a_custom_element_callback_reaction(HTML::CustomElementReactionNames::disconnectedCallback, move(empty_arguments));
844
0
        }
845
0
    }
846
847
    // 18. For each shadow-including descendant descendant of node, in shadow-including tree order, then:
848
0
    for_each_shadow_including_descendant([&](Node& descendant) {
849
        // 1. Run the removing steps with descendant
850
0
        descendant.removed_from(nullptr);
851
852
        // 2. If descendant is custom and isParentConnected is true, then enqueue a custom element callback reaction with descendant,
853
        //    callback name "disconnectedCallback", and an empty argument list.
854
0
        if (is<DOM::Element>(descendant)) {
855
0
            auto& element = static_cast<DOM::Element&>(descendant);
856
857
0
            if (element.is_custom() && is_parent_connected) {
858
0
                JS::MarkedVector<JS::Value> empty_arguments { vm().heap() };
859
0
                element.enqueue_a_custom_element_callback_reaction(HTML::CustomElementReactionNames::disconnectedCallback, move(empty_arguments));
860
0
            }
861
0
        }
862
863
0
        return TraversalDecision::Continue;
864
0
    });
865
866
    // 19. For each inclusive ancestor inclusiveAncestor of parent, and then for each registered of inclusiveAncestor’s registered observer list,
867
    //     if registered’s options["subtree"] is true, then append a new transient registered observer
868
    //     whose observer is registered’s observer, options is registered’s options, and source is registered to node’s registered observer list.
869
0
    for (auto* inclusive_ancestor = parent; inclusive_ancestor; inclusive_ancestor = inclusive_ancestor->parent()) {
870
0
        if (!inclusive_ancestor->m_registered_observer_list)
871
0
            continue;
872
0
        for (auto& registered : *inclusive_ancestor->m_registered_observer_list) {
873
0
            if (registered->options().subtree) {
874
0
                auto transient_observer = TransientRegisteredObserver::create(registered->observer(), registered->options(), registered);
875
0
                add_registered_observer(move(transient_observer));
876
0
            }
877
0
        }
878
0
    }
879
880
    // 20. If suppress observers flag is unset, then queue a tree mutation record for parent with « », « node », oldPreviousSibling, and oldNextSibling.
881
0
    if (!suppress_observers) {
882
0
        parent->queue_tree_mutation_record({}, { *this }, old_previous_sibling.ptr(), old_next_sibling.ptr());
883
0
    }
884
885
    // 21. Run the children changed steps for parent.
886
0
    parent->children_changed();
887
888
0
    if (was_connected) {
889
        // Since the tree structure has changed, we need to invalidate both style and layout.
890
        // In the future, we should find a way to only invalidate the parts that actually need it.
891
892
        // FIXME: If there are any :has() selectors, we currently invalidate style for the whole document.
893
        //        We need to find a way to invalidate less!
894
0
        if (document().style_computer().has_has_selectors()) {
895
0
            document().invalidate_style(StyleInvalidationReason::NodeRemove);
896
0
        } else {
897
0
            invalidate_style(StyleInvalidationReason::NodeRemove);
898
0
        }
899
900
        // NOTE: If we didn't have a layout node before, rebuilding the layout tree isn't gonna give us one
901
        //       after we've been removed from the DOM.
902
0
        if (had_layout_node) {
903
0
            document().invalidate_layout_tree();
904
0
        }
905
0
    }
906
907
0
    document().bump_dom_tree_version();
908
0
}
909
910
// https://dom.spec.whatwg.org/#concept-node-replace
911
WebIDL::ExceptionOr<JS::NonnullGCPtr<Node>> Node::replace_child(JS::NonnullGCPtr<Node> node, JS::NonnullGCPtr<Node> child)
912
0
{
913
    // If parent is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException.
914
0
    if (!is<Document>(this) && !is<DocumentFragment>(this) && !is<Element>(this))
915
0
        return WebIDL::HierarchyRequestError::create(realm(), "Can only insert into a document, document fragment or element"_string);
916
917
    // 2. If node is a host-including inclusive ancestor of parent, then throw a "HierarchyRequestError" DOMException.
918
0
    if (node->is_host_including_inclusive_ancestor_of(*this))
919
0
        return WebIDL::HierarchyRequestError::create(realm(), "New node is an ancestor of this node"_string);
920
921
    // 3. If child’s parent is not parent, then throw a "NotFoundError" DOMException.
922
0
    if (child->parent() != this)
923
0
        return WebIDL::NotFoundError::create(realm(), "This node is not the parent of the given child"_string);
924
925
    // FIXME: All the following "Invalid node type for insertion" messages could be more descriptive.
926
927
    // 4. If node is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException.
928
0
    if (!is<DocumentFragment>(*node) && !is<DocumentType>(*node) && !is<Element>(*node) && !is<Text>(*node) && !is<Comment>(*node) && !is<ProcessingInstruction>(*node))
929
0
        return WebIDL::HierarchyRequestError::create(realm(), "Invalid node type for insertion"_string);
930
931
    // 5. If either node is a Text node and parent is a document, or node is a doctype and parent is not a document, then throw a "HierarchyRequestError" DOMException.
932
0
    if ((is<Text>(*node) && is<Document>(this)) || (is<DocumentType>(*node) && !is<Document>(this)))
933
0
        return WebIDL::HierarchyRequestError::create(realm(), "Invalid node type for insertion"_string);
934
935
    // If parent is a document, and any of the statements below, switched on the interface node implements, are true, then throw a "HierarchyRequestError" DOMException.
936
0
    if (is<Document>(this)) {
937
        // DocumentFragment
938
0
        if (is<DocumentFragment>(*node)) {
939
            // If node has more than one element child or has a Text node child.
940
            // Otherwise, if node has one element child and either parent has an element child that is not child or a doctype is following child.
941
0
            auto node_element_child_count = verify_cast<DocumentFragment>(*node).child_element_count();
942
0
            if ((node_element_child_count > 1 || node->has_child_of_type<Text>())
943
0
                || (node_element_child_count == 1 && (first_child_of_type<Element>() != child || child->has_following_node_of_type_in_tree_order<DocumentType>()))) {
944
0
                return WebIDL::HierarchyRequestError::create(realm(), "Invalid node type for insertion"_string);
945
0
            }
946
0
        } else if (is<Element>(*node)) {
947
            // Element
948
            // parent has an element child that is not child or a doctype is following child.
949
0
            if (first_child_of_type<Element>() != child || child->has_following_node_of_type_in_tree_order<DocumentType>())
950
0
                return WebIDL::HierarchyRequestError::create(realm(), "Invalid node type for insertion"_string);
951
0
        } else if (is<DocumentType>(*node)) {
952
            // DocumentType
953
            // parent has a doctype child that is not child, or an element is preceding child.
954
0
            if (first_child_of_type<DocumentType>() != node || child->has_preceding_node_of_type_in_tree_order<Element>())
955
0
                return WebIDL::HierarchyRequestError::create(realm(), "Invalid node type for insertion"_string);
956
0
        }
957
0
    }
958
959
    // 7. Let referenceChild be child’s next sibling.
960
0
    JS::GCPtr<Node> reference_child = child->next_sibling();
961
962
    // 8. If referenceChild is node, then set referenceChild to node’s next sibling.
963
0
    if (reference_child == node)
964
0
        reference_child = node->next_sibling();
965
966
    // 9. Let previousSibling be child’s previous sibling.
967
0
    JS::GCPtr<Node> previous_sibling = child->previous_sibling();
968
969
    // 10. Let removedNodes be the empty set.
970
0
    Vector<JS::Handle<Node>> removed_nodes;
971
972
    // 11. If child’s parent is non-null, then:
973
    // NOTE: The above can only be false if child is node.
974
0
    if (child->parent()) {
975
        // 1. Set removedNodes to « child ».
976
0
        removed_nodes.append(JS::make_handle(*child));
977
978
        // 2. Remove child with the suppress observers flag set.
979
0
        child->remove(true);
980
0
    }
981
982
    // 12. Let nodes be node’s children if node is a DocumentFragment node; otherwise « node ».
983
0
    Vector<JS::Handle<Node>> nodes;
984
0
    if (is<DocumentFragment>(*node))
985
0
        nodes = node->children_as_vector();
986
0
    else
987
0
        nodes.append(JS::make_handle(*node));
988
989
    // 13. Insert node into parent before referenceChild with the suppress observers flag set.
990
0
    insert_before(node, reference_child, true);
991
992
    // 14. Queue a tree mutation record for parent with nodes, removedNodes, previousSibling, and referenceChild.
993
0
    queue_tree_mutation_record(move(nodes), move(removed_nodes), previous_sibling.ptr(), reference_child.ptr());
994
995
    // 15. Return child.
996
0
    return child;
997
0
}
998
999
// https://dom.spec.whatwg.org/#concept-node-clone
1000
WebIDL::ExceptionOr<JS::NonnullGCPtr<Node>> Node::clone_node(Document* document, bool clone_children)
1001
0
{
1002
    // 1. If document is not given, let document be node’s node document.
1003
0
    if (!document)
1004
0
        document = m_document.ptr();
1005
0
    JS::GCPtr<Node> copy;
1006
1007
    // 2. If node is an element, then:
1008
0
    if (is<Element>(this)) {
1009
        // 1. Let copy be the result of creating an element, given document, node’s local name, node’s namespace, node’s namespace prefix, and node’s is value, with the synchronous custom elements flag unset.
1010
0
        auto& element = *verify_cast<Element>(this);
1011
0
        auto element_copy = DOM::create_element(*document, element.local_name(), element.namespace_uri(), element.prefix(), element.is_value(), false).release_value_but_fixme_should_propagate_errors();
1012
1013
        // 2. For each attribute in node’s attribute list:
1014
0
        element.for_each_attribute([&](auto& name, auto& value) {
1015
            // 1. Let copyAttribute be a clone of attribute.
1016
            // 2. Append copyAttribute to copy.
1017
0
            element_copy->append_attribute(name, value);
1018
0
        });
1019
0
        copy = move(element_copy);
1020
1021
0
    }
1022
    // 3. Otherwise, let copy be a node that implements the same interfaces as node, and fulfills these additional requirements, switching on the interface node implements:
1023
0
    else if (is<Document>(this)) {
1024
        // Document
1025
0
        auto document_ = verify_cast<Document>(this);
1026
0
        auto document_copy = Document::create(this->realm(), document_->url());
1027
1028
        // Set copy’s encoding, content type, URL, origin, type, and mode to those of node.
1029
0
        document_copy->set_encoding(document_->encoding());
1030
0
        document_copy->set_content_type(document_->content_type());
1031
0
        document_copy->set_url(document_->url());
1032
0
        document_copy->set_origin(document_->origin());
1033
0
        document_copy->set_document_type(document_->document_type());
1034
0
        document_copy->set_quirks_mode(document_->mode());
1035
0
        copy = move(document_copy);
1036
0
    } else if (is<DocumentType>(this)) {
1037
        // DocumentType
1038
0
        auto document_type = verify_cast<DocumentType>(this);
1039
0
        auto document_type_copy = heap().allocate<DocumentType>(realm(), *document);
1040
1041
        // Set copy’s name, public ID, and system ID to those of node.
1042
0
        document_type_copy->set_name(document_type->name());
1043
0
        document_type_copy->set_public_id(document_type->public_id());
1044
0
        document_type_copy->set_system_id(document_type->system_id());
1045
0
        copy = move(document_type_copy);
1046
0
    } else if (is<Attr>(this)) {
1047
        // Attr
1048
        // Set copy’s namespace, namespace prefix, local name, and value to those of node.
1049
0
        auto& attr = static_cast<Attr&>(*this);
1050
0
        copy = attr.clone(*document);
1051
0
    }
1052
    // NOTE: is<Text>() currently returns true only for text nodes, not for descendant types of Text.
1053
0
    else if (is<Text>(this) || is<CDATASection>(this)) {
1054
        // Text
1055
0
        auto& text = static_cast<Text&>(*this);
1056
1057
        // Set copy’s data to that of node.
1058
0
        auto text_copy = heap().allocate<Text>(realm(), *document, text.data());
1059
0
        copy = move(text_copy);
1060
0
    } else if (is<Comment>(this)) {
1061
        // Comment
1062
0
        auto comment = verify_cast<Comment>(this);
1063
1064
        // Set copy’s data to that of node.
1065
0
        auto comment_copy = heap().allocate<Comment>(realm(), *document, comment->data());
1066
0
        copy = move(comment_copy);
1067
0
    } else if (is<ProcessingInstruction>(this)) {
1068
        // ProcessingInstruction
1069
0
        auto processing_instruction = verify_cast<ProcessingInstruction>(this);
1070
1071
        // Set copy’s target and data to those of node.
1072
0
        auto processing_instruction_copy = heap().allocate<ProcessingInstruction>(realm(), *document, processing_instruction->data(), processing_instruction->target());
1073
0
        copy = processing_instruction_copy;
1074
0
    }
1075
    // Otherwise, Do nothing.
1076
0
    else if (is<DocumentFragment>(this)) {
1077
0
        copy = heap().allocate<DocumentFragment>(realm(), *document);
1078
0
    }
1079
1080
    // FIXME: 4. Set copy’s node document and document to copy, if copy is a document, and set copy’s node document to document otherwise.
1081
1082
    // 5. Run any cloning steps defined for node in other applicable specifications and pass copy, node, document and the clone children flag if set, as parameters.
1083
0
    TRY(cloned(*copy, clone_children));
1084
1085
    // 6. If the clone children flag is set, clone all the children of node and append them to copy, with document as specified and the clone children flag being set.
1086
0
    if (clone_children) {
1087
0
        for (auto child = first_child(); child; child = child->next_sibling()) {
1088
0
            TRY(copy->append_child(TRY(child->clone_node(document, true))));
1089
0
        }
1090
0
    }
1091
1092
    // 7. If node is a shadow host whose shadow root’s clonable is true:
1093
0
    if (is_element() && static_cast<Element const&>(*this).is_shadow_host() && static_cast<Element const&>(*this).shadow_root()->clonable()) {
1094
        // 1. Assert: copy is not a shadow host.
1095
0
        VERIFY(!copy->is_element() || !static_cast<Element const&>(*copy).is_shadow_host());
1096
1097
        // 2. Run attach a shadow root with copy, node’s shadow root’s mode, true, node’s shadow root’s serializable,
1098
        //    node’s shadow root’s delegates focus, and node’s shadow root’s slot assignment.
1099
0
        auto& node_shadow_root = *static_cast<Element&>(*this).shadow_root();
1100
0
        TRY(static_cast<Element&>(*copy).attach_a_shadow_root(node_shadow_root.mode(), true, node_shadow_root.serializable(), node_shadow_root.delegates_focus(), node_shadow_root.slot_assignment()));
1101
1102
        // 3. Set copy’s shadow root’s declarative to node’s shadow root’s declarative.
1103
0
        static_cast<Element&>(*copy).shadow_root()->set_declarative(node_shadow_root.declarative());
1104
1105
        // 4. For each child child of node’s shadow root, in tree order:
1106
        //    append the result of cloning child with document and the clone children flag set, to copy’s shadow root.
1107
0
        for (auto child = node_shadow_root.first_child(); child; child = child->next_sibling()) {
1108
0
            TRY(static_cast<Element&>(*copy).shadow_root()->append_child(TRY(child->clone_node(document, true))));
1109
0
        }
1110
0
    }
1111
1112
    // 7. Return copy.
1113
0
    VERIFY(copy);
1114
0
    return JS::NonnullGCPtr { *copy };
1115
0
}
1116
1117
// https://dom.spec.whatwg.org/#dom-node-clonenode
1118
WebIDL::ExceptionOr<JS::NonnullGCPtr<Node>> Node::clone_node_binding(bool deep)
1119
0
{
1120
    // 1. If this is a shadow root, then throw a "NotSupportedError" DOMException.
1121
0
    if (is<ShadowRoot>(*this))
1122
0
        return WebIDL::NotSupportedError::create(realm(), "Cannot clone shadow root"_string);
1123
1124
    // 2. Return a clone of this, with the clone children flag set if deep is true.
1125
0
    return clone_node(nullptr, deep);
1126
0
}
1127
1128
void Node::set_document(Badge<Document>, Document& document)
1129
0
{
1130
0
    if (m_document.ptr() == &document)
1131
0
        return;
1132
1133
0
    m_document = &document;
1134
1135
0
    if (needs_style_update() || child_needs_style_update()) {
1136
        // NOTE: We unset and reset the "needs style update" flag here.
1137
        //       This ensures that there's a pending style update in the new document
1138
        //       that will eventually assign some style to this node if needed.
1139
0
        set_needs_style_update(false);
1140
0
        set_needs_style_update(true);
1141
0
    }
1142
0
}
1143
1144
bool Node::is_editable() const
1145
0
{
1146
0
    return parent() && parent()->is_editable();
1147
0
}
1148
1149
void Node::set_layout_node(Badge<Layout::Node>, JS::NonnullGCPtr<Layout::Node> layout_node)
1150
0
{
1151
0
    m_layout_node = layout_node;
1152
0
}
1153
1154
void Node::detach_layout_node(Badge<Layout::TreeBuilder>)
1155
0
{
1156
0
    m_layout_node = nullptr;
1157
0
}
1158
1159
EventTarget* Node::get_parent(Event const&)
1160
0
{
1161
    // A node’s get the parent algorithm, given an event, returns the node’s assigned slot, if node is assigned;
1162
    // otherwise node’s parent.
1163
0
    if (auto assigned_slot = assigned_slot_for_node(*this))
1164
0
        return assigned_slot.ptr();
1165
1166
0
    return parent();
1167
0
}
1168
1169
void Node::set_needs_style_update(bool value)
1170
0
{
1171
0
    if (m_needs_style_update == value)
1172
0
        return;
1173
0
    m_needs_style_update = value;
1174
1175
0
    if (m_needs_style_update) {
1176
0
        for (auto* ancestor = parent_or_shadow_host(); ancestor; ancestor = ancestor->parent_or_shadow_host()) {
1177
0
            if (ancestor->m_child_needs_style_update)
1178
0
                break;
1179
0
            ancestor->m_child_needs_style_update = true;
1180
0
        }
1181
0
        document().schedule_style_update();
1182
0
    }
1183
0
}
1184
1185
void Node::inserted()
1186
0
{
1187
0
    set_needs_style_update(true);
1188
0
}
1189
1190
void Node::removed_from(Node*)
1191
0
{
1192
0
    m_layout_node = nullptr;
1193
0
    m_paintable = nullptr;
1194
0
}
1195
1196
ParentNode* Node::parent_or_shadow_host()
1197
0
{
1198
0
    if (is<ShadowRoot>(*this))
1199
0
        return static_cast<ShadowRoot&>(*this).host();
1200
0
    return verify_cast<ParentNode>(parent());
1201
0
}
1202
1203
Element* Node::parent_or_shadow_host_element()
1204
0
{
1205
0
    if (is<ShadowRoot>(*this))
1206
0
        return static_cast<ShadowRoot&>(*this).host();
1207
0
    if (!parent())
1208
0
        return nullptr;
1209
0
    if (is<Element>(*parent()))
1210
0
        return static_cast<Element*>(parent());
1211
0
    if (is<ShadowRoot>(*parent()))
1212
0
        return static_cast<ShadowRoot&>(*parent()).host();
1213
0
    return nullptr;
1214
0
}
1215
1216
Slottable Node::as_slottable()
1217
0
{
1218
0
    VERIFY(is_slottable());
1219
1220
0
    if (is_element())
1221
0
        return JS::NonnullGCPtr { static_cast<Element&>(*this) };
1222
0
    return JS::NonnullGCPtr { static_cast<Text&>(*this) };
1223
0
}
1224
1225
JS::NonnullGCPtr<NodeList> Node::child_nodes()
1226
0
{
1227
0
    if (!m_child_nodes) {
1228
0
        m_child_nodes = LiveNodeList::create(realm(), *this, LiveNodeList::Scope::Children, [](auto&) {
1229
0
            return true;
1230
0
        });
1231
0
    }
1232
0
    return *m_child_nodes;
1233
0
}
1234
1235
Vector<JS::Handle<Node>> Node::children_as_vector() const
1236
0
{
1237
0
    Vector<JS::Handle<Node>> nodes;
1238
1239
0
    for_each_child([&](auto& child) {
1240
0
        nodes.append(JS::make_handle(child));
1241
0
        return IterationDecision::Continue;
1242
0
    });
1243
1244
0
    return nodes;
1245
0
}
1246
1247
void Node::remove_all_children(bool suppress_observers)
1248
0
{
1249
0
    while (JS::GCPtr<Node> child = first_child())
1250
0
        child->remove(suppress_observers);
1251
0
}
1252
1253
// https://dom.spec.whatwg.org/#dom-node-comparedocumentposition
1254
u16 Node::compare_document_position(JS::GCPtr<Node> other)
1255
0
{
1256
    // 1. If this is other, then return zero.
1257
0
    if (this == other.ptr())
1258
0
        return DOCUMENT_POSITION_EQUAL;
1259
1260
    // 2. Let node1 be other and node2 be this.
1261
0
    Node* node1 = other.ptr();
1262
0
    Node* node2 = this;
1263
1264
    // 3. Let attr1 and attr2 be null.
1265
0
    Attr* attr1 = nullptr;
1266
0
    Attr* attr2 = nullptr;
1267
1268
    // 4. If node1 is an attribute, then set attr1 to node1 and node1 to attr1’s element.
1269
0
    if (is<Attr>(node1)) {
1270
0
        attr1 = verify_cast<Attr>(node1);
1271
0
        node1 = const_cast<Element*>(attr1->owner_element());
1272
0
    }
1273
1274
    // 5. If node2 is an attribute, then:
1275
0
    if (is<Attr>(node2)) {
1276
        // 1. Set attr2 to node2 and node2 to attr2’s element.
1277
0
        attr2 = verify_cast<Attr>(node2);
1278
0
        node2 = const_cast<Element*>(attr2->owner_element());
1279
1280
        // 2. If attr1 and node1 are non-null, and node2 is node1, then:
1281
0
        if (attr1 && node1 && node2 == node1) {
1282
            // FIXME: 1. For each attr in node2’s attribute list:
1283
            //     1. If attr equals attr1, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_PRECEDING.
1284
            //     2. If attr equals attr2, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_FOLLOWING.
1285
0
        }
1286
0
    }
1287
1288
    // 6. If node1 or node2 is null, or node1’s root is not node2’s root, then return the result of adding
1289
    // DOCUMENT_POSITION_DISCONNECTED, DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC, and either DOCUMENT_POSITION_PRECEDING or DOCUMENT_POSITION_FOLLOWING, with the constraint that this is to be consistent, together.
1290
0
    if ((node1 == nullptr || node2 == nullptr) || (&node1->root() != &node2->root()))
1291
0
        return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | (node1 > node2 ? DOCUMENT_POSITION_PRECEDING : DOCUMENT_POSITION_FOLLOWING);
1292
1293
0
    Vector<Node*> node1_ancestors;
1294
0
    for (auto* node = node1; node; node = node->parent())
1295
0
        node1_ancestors.append(node);
1296
1297
0
    Vector<Node*> node2_ancestors;
1298
0
    for (auto* node = node2; node; node = node->parent())
1299
0
        node2_ancestors.append(node);
1300
1301
0
    auto it_node1_ancestors = node1_ancestors.rbegin();
1302
0
    auto it_node2_ancestors = node2_ancestors.rbegin();
1303
    // Walk ancestor chains of both nodes starting from root
1304
0
    while (it_node1_ancestors != node1_ancestors.rend() && it_node2_ancestors != node2_ancestors.rend()) {
1305
0
        auto* ancestor1 = *it_node1_ancestors;
1306
0
        auto* ancestor2 = *it_node2_ancestors;
1307
1308
        // If ancestors of nodes at the same level in the tree are different then preceding node is the one with lower sibling position
1309
0
        if (ancestor1 != ancestor2) {
1310
0
            auto* node = ancestor1;
1311
0
            while (node) {
1312
0
                if (node == ancestor2)
1313
0
                    return DOCUMENT_POSITION_PRECEDING;
1314
0
                node = node->next_sibling();
1315
0
            }
1316
0
            return DOCUMENT_POSITION_FOLLOWING;
1317
0
        }
1318
1319
0
        it_node1_ancestors++;
1320
0
        it_node2_ancestors++;
1321
0
    }
1322
1323
    // NOTE: If nodes in ancestors chains are the same but one chain is longer, then one node is ancestor of another.
1324
    //       The node with shorter ancestors chain is the ancestor.
1325
    //       The node with longer ancestors chain is the descendant.
1326
1327
    // 7. If node1 is an ancestor of node2 and attr1 is null, or node1 is node2 and attr2 is non-null, then return the result of adding DOCUMENT_POSITION_CONTAINS to DOCUMENT_POSITION_PRECEDING.
1328
0
    if ((node1_ancestors.size() < node2_ancestors.size() && !attr1) || (node1 == node2 && attr2))
1329
0
        return DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING;
1330
1331
    // 8. If node1 is a descendant of node2 and attr2 is null, or node1 is node2 and attr1 is non-null, then return the result of adding DOCUMENT_POSITION_CONTAINED_BY to DOCUMENT_POSITION_FOLLOWING.
1332
0
    if ((node1_ancestors.size() > node2_ancestors.size() && !attr2) || (node1 == node2 && attr1))
1333
0
        return DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING;
1334
1335
    // 9. If node1 is preceding node2, then return DOCUMENT_POSITION_PRECEDING.
1336
0
    if (node1_ancestors.size() < node2_ancestors.size())
1337
0
        return DOCUMENT_POSITION_PRECEDING;
1338
1339
    // 10. Return DOCUMENT_POSITION_FOLLOWING.
1340
0
    return DOCUMENT_POSITION_FOLLOWING;
1341
0
}
1342
1343
// https://dom.spec.whatwg.org/#concept-tree-host-including-inclusive-ancestor
1344
bool Node::is_host_including_inclusive_ancestor_of(Node const& other) const
1345
0
{
1346
    // An object A is a host-including inclusive ancestor of an object B,
1347
    // if either A is an inclusive ancestor of B,
1348
0
    if (is_inclusive_ancestor_of(other))
1349
0
        return true;
1350
1351
    // or if B’s root has a non-null host and A is a host-including inclusive ancestor of B’s root’s host
1352
0
    if (is<DocumentFragment>(other.root())
1353
0
        && static_cast<DocumentFragment const&>(other.root()).host()
1354
0
        && is_inclusive_ancestor_of(*static_cast<DocumentFragment const&>(other.root()).host())) {
1355
0
        return true;
1356
0
    }
1357
0
    return false;
1358
0
}
1359
1360
// https://dom.spec.whatwg.org/#dom-node-ownerdocument
1361
JS::GCPtr<Document> Node::owner_document() const
1362
0
{
1363
    // The ownerDocument getter steps are to return null, if this is a document; otherwise this’s node document.
1364
0
    if (is_document())
1365
0
        return nullptr;
1366
0
    return m_document;
1367
0
}
1368
1369
// This function tells us whether a node is interesting enough to show up
1370
// in the DOM inspector. This hides two things:
1371
// - Non-rendered whitespace
1372
// - Rendered whitespace between block-level elements
1373
bool Node::is_uninteresting_whitespace_node() const
1374
0
{
1375
0
    if (!is<Text>(*this))
1376
0
        return false;
1377
0
    if (!static_cast<Text const&>(*this).data().bytes_as_string_view().is_whitespace())
1378
0
        return false;
1379
0
    if (!layout_node())
1380
0
        return true;
1381
0
    if (auto parent = layout_node()->parent(); parent && parent->is_anonymous())
1382
0
        return true;
1383
0
    return false;
1384
0
}
1385
1386
void Node::serialize_tree_as_json(JsonObjectSerializer<StringBuilder>& object) const
1387
0
{
1388
0
    MUST(object.add("name"sv, node_name()));
1389
0
    MUST(object.add("id"sv, unique_id()));
1390
0
    if (is_document()) {
1391
0
        MUST(object.add("type"sv, "document"));
1392
0
    } else if (is_element()) {
1393
0
        MUST(object.add("type"sv, "element"));
1394
1395
0
        auto const* element = static_cast<DOM::Element const*>(this);
1396
0
        if (element->has_attributes()) {
1397
0
            auto attributes = MUST(object.add_object("attributes"sv));
1398
0
            element->for_each_attribute([&attributes](auto& name, auto& value) {
1399
0
                MUST(attributes.add(name, value));
1400
0
            });
1401
0
            MUST(attributes.finish());
1402
0
        }
1403
1404
0
        if (element->is_navigable_container()) {
1405
0
            auto const* container = static_cast<HTML::NavigableContainer const*>(element);
1406
0
            if (auto const* content_document = container->content_document()) {
1407
0
                auto children = MUST(object.add_array("children"sv));
1408
0
                JsonObjectSerializer<StringBuilder> content_document_object = MUST(children.add_object());
1409
0
                content_document->serialize_tree_as_json(content_document_object);
1410
0
                MUST(content_document_object.finish());
1411
0
                MUST(children.finish());
1412
0
            }
1413
0
        }
1414
0
    } else if (is_text()) {
1415
0
        MUST(object.add("type"sv, "text"));
1416
1417
0
        auto text_node = static_cast<DOM::Text const*>(this);
1418
0
        MUST(object.add("text"sv, text_node->data()));
1419
0
    } else if (is_comment()) {
1420
0
        MUST(object.add("type"sv, "comment"sv));
1421
0
        MUST(object.add("data"sv, static_cast<DOM::Comment const&>(*this).data()));
1422
0
    } else if (is_shadow_root()) {
1423
0
        MUST(object.add("type"sv, "shadow-root"));
1424
0
        MUST(object.add("mode"sv, static_cast<DOM::ShadowRoot const&>(*this).mode() == Bindings::ShadowRootMode::Open ? "open"sv : "closed"sv));
1425
0
    }
1426
1427
0
    MUST((object.add("visible"sv, !!layout_node())));
1428
1429
0
    auto const* element = is_element() ? static_cast<DOM::Element const*>(this) : nullptr;
1430
1431
0
    if (has_child_nodes()
1432
0
        || (element && (element->is_shadow_host() || element->has_pseudo_elements()))) {
1433
0
        auto children = MUST(object.add_array("children"sv));
1434
0
        auto add_child = [&children](DOM::Node const& child) {
1435
0
            if (child.is_uninteresting_whitespace_node())
1436
0
                return IterationDecision::Continue;
1437
0
            JsonObjectSerializer<StringBuilder> child_object = MUST(children.add_object());
1438
0
            child.serialize_tree_as_json(child_object);
1439
0
            MUST(child_object.finish());
1440
0
            return IterationDecision::Continue;
1441
0
        };
1442
0
        for_each_child(add_child);
1443
1444
0
        if (element) {
1445
            // Pseudo-elements don't have DOM nodes,so we have to add them separately.
1446
0
            element->serialize_pseudo_elements_as_json(children);
1447
1448
0
            if (element->is_shadow_host())
1449
0
                add_child(*element->shadow_root());
1450
0
        }
1451
1452
0
        MUST(children.finish());
1453
0
    }
1454
0
}
1455
1456
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-n-script
1457
bool Node::is_scripting_enabled() const
1458
0
{
1459
    // Scripting is enabled for a node node if node's node document's browsing context is non-null, and scripting is enabled for node's relevant settings object.
1460
0
    return document().browsing_context() && const_cast<Document&>(document()).relevant_settings_object().is_scripting_enabled();
1461
0
}
1462
1463
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-n-noscript
1464
bool Node::is_scripting_disabled() const
1465
0
{
1466
    // Scripting is disabled for a node when scripting is not enabled, i.e., when its node document's browsing context is null or when scripting is disabled for its relevant settings object.
1467
0
    return !is_scripting_enabled();
1468
0
}
1469
1470
// https://dom.spec.whatwg.org/#dom-node-contains
1471
bool Node::contains(JS::GCPtr<Node> other) const
1472
0
{
1473
    // The contains(other) method steps are to return true if other is an inclusive descendant of this; otherwise false (including when other is null).
1474
0
    return other && other->is_inclusive_descendant_of(*this);
1475
0
}
1476
1477
// https://dom.spec.whatwg.org/#concept-shadow-including-descendant
1478
bool Node::is_shadow_including_descendant_of(Node const& other) const
1479
0
{
1480
    // An object A is a shadow-including descendant of an object B,
1481
    // if A is a descendant of B,
1482
0
    if (is_descendant_of(other))
1483
0
        return true;
1484
1485
    // or A’s root is a shadow root
1486
0
    if (!is<ShadowRoot>(root()))
1487
0
        return false;
1488
1489
    // and A’s root’s host is a shadow-including inclusive descendant of B.
1490
0
    auto& shadow_root = verify_cast<ShadowRoot>(root());
1491
0
    return shadow_root.host() && shadow_root.host()->is_shadow_including_inclusive_descendant_of(other);
1492
0
}
1493
1494
// https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-descendant
1495
bool Node::is_shadow_including_inclusive_descendant_of(Node const& other) const
1496
0
{
1497
    // A shadow-including inclusive descendant is an object or one of its shadow-including descendants.
1498
0
    return &other == this || is_shadow_including_descendant_of(other);
1499
0
}
1500
1501
// https://dom.spec.whatwg.org/#concept-shadow-including-ancestor
1502
bool Node::is_shadow_including_ancestor_of(Node const& other) const
1503
0
{
1504
    // An object A is a shadow-including ancestor of an object B, if and only if B is a shadow-including descendant of A.
1505
0
    return other.is_shadow_including_descendant_of(*this);
1506
0
}
1507
1508
// https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor
1509
bool Node::is_shadow_including_inclusive_ancestor_of(Node const& other) const
1510
0
{
1511
    // A shadow-including inclusive ancestor is an object or one of its shadow-including ancestors.
1512
0
    return other.is_shadow_including_inclusive_descendant_of(*this);
1513
0
}
1514
1515
// https://dom.spec.whatwg.org/#concept-node-replace-all
1516
void Node::replace_all(JS::GCPtr<Node> node)
1517
0
{
1518
    // 1. Let removedNodes be parent’s children.
1519
0
    auto removed_nodes = children_as_vector();
1520
1521
    // 2. Let addedNodes be the empty set.
1522
0
    Vector<JS::Handle<Node>> added_nodes;
1523
1524
    // 3. If node is a DocumentFragment node, then set addedNodes to node’s children.
1525
0
    if (node && is<DocumentFragment>(*node)) {
1526
0
        added_nodes = node->children_as_vector();
1527
0
    }
1528
    // 4. Otherwise, if node is non-null, set addedNodes to « node ».
1529
0
    else if (node) {
1530
0
        added_nodes.append(JS::make_handle(*node));
1531
0
    }
1532
1533
    // 5. Remove all parent’s children, in tree order, with the suppress observers flag set.
1534
0
    remove_all_children(true);
1535
1536
    // 6. If node is non-null, then insert node into parent before null with the suppress observers flag set.
1537
0
    if (node)
1538
0
        insert_before(*node, nullptr, true);
1539
1540
    // 7. If either addedNodes or removedNodes is not empty, then queue a tree mutation record for parent with addedNodes, removedNodes, null, and null.
1541
0
    if (!added_nodes.is_empty() || !removed_nodes.is_empty()) {
1542
0
        queue_tree_mutation_record(move(added_nodes), move(removed_nodes), nullptr, nullptr);
1543
0
    }
1544
0
}
1545
1546
// https://dom.spec.whatwg.org/#string-replace-all
1547
void Node::string_replace_all(String const& string)
1548
0
{
1549
    // 1. Let node be null.
1550
0
    JS::GCPtr<Node> node;
1551
1552
    // 2. If string is not the empty string, then set node to a new Text node whose data is string and node document is parent’s node document.
1553
0
    if (!string.is_empty())
1554
0
        node = heap().allocate<Text>(realm(), document(), string);
1555
1556
    // 3. Replace all with node within parent.
1557
0
    replace_all(node);
1558
0
}
1559
1560
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#fragment-serializing-algorithm-steps
1561
WebIDL::ExceptionOr<String> Node::serialize_fragment(DOMParsing::RequireWellFormed require_well_formed, FragmentSerializationMode fragment_serialization_mode) const
1562
0
{
1563
    // 1. Let context document be the value of node's node document.
1564
0
    auto const& context_document = document();
1565
1566
    // 2. If context document is an HTML document, return the result of HTML fragment serialization algorithm with node, false, and « ».
1567
0
    if (context_document.is_html_document())
1568
0
        return HTML::HTMLParser::serialize_html_fragment(*this, HTML::HTMLParser::SerializableShadowRoots::No, {}, fragment_serialization_mode);
1569
1570
    // 3. Return the XML serialization of node given require well-formed.
1571
    // AD-HOC: XML serialization algorithm returns the "outer" XML serialization of the node.
1572
    //         For inner, concatenate the serialization of all children.
1573
0
    if (fragment_serialization_mode == FragmentSerializationMode::Inner) {
1574
0
        StringBuilder markup;
1575
0
        for_each_child([&markup, require_well_formed](auto& child) {
1576
0
            auto child_markup = DOMParsing::serialize_node_to_xml_string(child, require_well_formed).release_value_but_fixme_should_propagate_errors();
1577
0
            markup.append(child_markup.bytes_as_string_view());
1578
0
            return IterationDecision::Continue;
1579
0
        });
1580
0
        return MUST(markup.to_string());
1581
0
    }
1582
0
    return DOMParsing::serialize_node_to_xml_string(*this, require_well_formed);
1583
0
}
1584
1585
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#unsafely-set-html
1586
WebIDL::ExceptionOr<void> Node::unsafely_set_html(Element& context_element, StringView html)
1587
0
{
1588
    // 1. Let newChildren be the result of the HTML fragment parsing algorithm given contextElement, html, and true.
1589
0
    auto new_children = HTML::HTMLParser::parse_html_fragment(context_element, html, HTML::HTMLParser::AllowDeclarativeShadowRoots::Yes);
1590
1591
    // 2. Let fragment be a new DocumentFragment whose node document is contextElement’s node document.
1592
0
    auto fragment = heap().allocate<DocumentFragment>(realm(), context_element.document());
1593
1594
    // 3. For each node in newChildren, append node to fragment.
1595
0
    for (auto& child : new_children)
1596
        // I don't know if this can throw here, but let's be safe.
1597
0
        (void)TRY(fragment->append_child(*child));
1598
1599
    // 4. Replace all with fragment within contextElement.
1600
0
    replace_all(fragment);
1601
1602
0
    return {};
1603
0
}
1604
1605
// https://dom.spec.whatwg.org/#dom-node-issamenode
1606
bool Node::is_same_node(Node const* other_node) const
1607
0
{
1608
    // The isSameNode(otherNode) method steps are to return true if otherNode is this; otherwise false.
1609
0
    return this == other_node;
1610
0
}
1611
1612
// https://dom.spec.whatwg.org/#dom-node-isequalnode
1613
bool Node::is_equal_node(Node const* other_node) const
1614
0
{
1615
    // The isEqualNode(otherNode) method steps are to return true if otherNode is non-null and this equals otherNode; otherwise false.
1616
0
    if (!other_node)
1617
0
        return false;
1618
1619
    // Fast path for testing a node against itself.
1620
0
    if (this == other_node)
1621
0
        return true;
1622
1623
    // A node A equals a node B if all of the following conditions are true:
1624
1625
    // A and B implement the same interfaces.
1626
0
    if (!node_name().equals_ignoring_ascii_case(other_node->node_name()))
1627
0
        return false;
1628
1629
    // The following are equal, switching on the interface A implements:
1630
0
    switch (node_type()) {
1631
0
    case (u16)NodeType::DOCUMENT_TYPE_NODE: {
1632
        // Its name, public ID, and system ID.
1633
0
        auto& this_doctype = verify_cast<DocumentType>(*this);
1634
0
        auto& other_doctype = verify_cast<DocumentType>(*other_node);
1635
0
        if (this_doctype.name() != other_doctype.name()
1636
0
            || this_doctype.public_id() != other_doctype.public_id()
1637
0
            || this_doctype.system_id() != other_doctype.system_id())
1638
0
            return false;
1639
0
        break;
1640
0
    }
1641
0
    case (u16)NodeType::ELEMENT_NODE: {
1642
        // Its namespace, namespace prefix, local name, and its attribute list’s size.
1643
0
        auto& this_element = verify_cast<Element>(*this);
1644
0
        auto& other_element = verify_cast<Element>(*other_node);
1645
0
        if (this_element.namespace_uri() != other_element.namespace_uri()
1646
0
            || this_element.prefix() != other_element.prefix()
1647
0
            || this_element.local_name() != other_element.local_name()
1648
0
            || this_element.attribute_list_size() != other_element.attribute_list_size())
1649
0
            return false;
1650
        // If A is an element, each attribute in its attribute list has an attribute that equals an attribute in B’s attribute list.
1651
0
        bool has_same_attributes = true;
1652
0
        this_element.for_each_attribute([&](auto const& attribute) {
1653
0
            if (other_element.get_attribute_ns(attribute.namespace_uri(), attribute.local_name()) != attribute.value())
1654
0
                has_same_attributes = false;
1655
0
        });
1656
0
        if (!has_same_attributes)
1657
0
            return false;
1658
0
        break;
1659
0
    }
1660
0
    case (u16)NodeType::COMMENT_NODE:
1661
0
    case (u16)NodeType::TEXT_NODE: {
1662
        // Its data.
1663
0
        auto& this_cdata = verify_cast<CharacterData>(*this);
1664
0
        auto& other_cdata = verify_cast<CharacterData>(*other_node);
1665
0
        if (this_cdata.data() != other_cdata.data())
1666
0
            return false;
1667
0
        break;
1668
0
    }
1669
0
    case (u16)NodeType::ATTRIBUTE_NODE: {
1670
        // Its namespace, local name, and value.
1671
0
        auto& this_attr = verify_cast<Attr>(*this);
1672
0
        auto& other_attr = verify_cast<Attr>(*other_node);
1673
0
        if (this_attr.namespace_uri() != other_attr.namespace_uri())
1674
0
            return false;
1675
0
        if (this_attr.local_name() != other_attr.local_name())
1676
0
            return false;
1677
0
        if (this_attr.value() != other_attr.value())
1678
0
            return false;
1679
0
        break;
1680
0
    }
1681
0
    case (u16)NodeType::PROCESSING_INSTRUCTION_NODE: {
1682
        // Its target and data.
1683
0
        auto& this_processing_instruction = verify_cast<ProcessingInstruction>(*this);
1684
0
        auto& other_processing_instruction = verify_cast<ProcessingInstruction>(*other_node);
1685
0
        if (this_processing_instruction.target() != other_processing_instruction.target())
1686
0
            return false;
1687
0
        if (this_processing_instruction.data() != other_processing_instruction.data())
1688
0
            return false;
1689
0
        break;
1690
0
    }
1691
0
    default:
1692
0
        break;
1693
0
    }
1694
1695
    // A and B have the same number of children.
1696
0
    if (child_count() != other_node->child_count())
1697
0
        return false;
1698
1699
    // Each child of A equals the child of B at the identical index.
1700
0
    auto* this_child = first_child();
1701
0
    auto* other_child = other_node->first_child();
1702
0
    while (this_child) {
1703
0
        VERIFY(other_child);
1704
0
        if (!this_child->is_equal_node(other_child))
1705
0
            return false;
1706
1707
0
        this_child = this_child->next_sibling();
1708
0
        other_child = other_child->next_sibling();
1709
0
    }
1710
1711
0
    return true;
1712
0
}
1713
1714
// https://dom.spec.whatwg.org/#locate-a-namespace
1715
Optional<String> Node::locate_a_namespace(Optional<String> const& prefix) const
1716
0
{
1717
    // To locate a namespace for a node using prefix, switch on the interface node implements:
1718
1719
    // Element
1720
0
    if (is<Element>(*this)) {
1721
        // 1. If prefix is "xml", then return the XML namespace.
1722
0
        if (prefix == "xml")
1723
0
            return Web::Namespace::XML.to_string();
1724
1725
        // 2. If prefix is "xmlns", then return the XMLNS namespace.
1726
0
        if (prefix == "xmlns")
1727
0
            return Web::Namespace::XMLNS.to_string();
1728
1729
        // 3. If its namespace is non-null and its namespace prefix is prefix, then return namespace.
1730
0
        auto& element = verify_cast<Element>(*this);
1731
0
        if (element.namespace_uri().has_value() && element.prefix() == prefix)
1732
0
            return element.namespace_uri()->to_string();
1733
1734
        // 4. If it has an attribute whose namespace is the XMLNS namespace, namespace prefix is "xmlns", and local name is prefix,
1735
        //    or if prefix is null and it has an attribute whose namespace is the XMLNS namespace, namespace prefix is null,
1736
        //    and local name is "xmlns", then return its value if it is not the empty string, and null otherwise.
1737
0
        if (auto* attributes = element.attributes()) {
1738
0
            for (size_t i = 0; i < attributes->length(); ++i) {
1739
0
                auto& attr = *attributes->item(i);
1740
0
                if (attr.namespace_uri() == Web::Namespace::XMLNS) {
1741
0
                    if ((attr.prefix() == "xmlns" && attr.local_name() == prefix) || (!prefix.has_value() && !attr.prefix().has_value() && attr.local_name() == "xmlns")) {
1742
0
                        auto value = attr.value();
1743
0
                        if (!value.is_empty())
1744
0
                            return value;
1745
1746
0
                        return {};
1747
0
                    }
1748
0
                }
1749
0
            }
1750
0
        }
1751
1752
        // 5. If its parent element is null, then return null.
1753
0
        auto* parent_element = element.parent_element();
1754
0
        if (!element.parent_element())
1755
0
            return {};
1756
1757
        // 6. Return the result of running locate a namespace on its parent element using prefix.
1758
0
        return parent_element->locate_a_namespace(prefix);
1759
0
    }
1760
1761
    // Document
1762
0
    if (is<Document>(*this)) {
1763
        // 1. If its document element is null, then return null.
1764
0
        auto* document_element = verify_cast<Document>(*this).document_element();
1765
0
        if (!document_element)
1766
0
            return {};
1767
1768
        // 2. Return the result of running locate a namespace on its document element using prefix.
1769
0
        return document_element->locate_a_namespace(prefix);
1770
0
    }
1771
1772
    // DocumentType
1773
    // DocumentFragment
1774
0
    if (is<DocumentType>(*this) || is<DocumentFragment>(*this)) {
1775
        // Return null.
1776
0
        return {};
1777
0
    }
1778
1779
    // Attr
1780
0
    if (is<Attr>(*this)) {
1781
        // 1. If its element is null, then return null.
1782
0
        auto* element = verify_cast<Attr>(*this).owner_element();
1783
0
        if (!element)
1784
0
            return {};
1785
1786
        // 2. Return the result of running locate a namespace on its element using prefix.
1787
0
        return element->locate_a_namespace(prefix);
1788
0
    }
1789
1790
    // Otherwise
1791
    // 1. If its parent element is null, then return null.
1792
0
    auto* parent_element = this->parent_element();
1793
0
    if (!parent_element)
1794
0
        return {};
1795
1796
    // 2. Return the result of running locate a namespace on its parent element using prefix.
1797
0
    return parent_element->locate_a_namespace(prefix);
1798
0
}
1799
1800
// https://dom.spec.whatwg.org/#dom-node-lookupnamespaceuri
1801
Optional<String> Node::lookup_namespace_uri(Optional<String> prefix) const
1802
0
{
1803
    // 1. If prefix is the empty string, then set it to null.
1804
0
    if (prefix.has_value() && prefix->is_empty())
1805
0
        prefix = {};
1806
1807
    // 2. Return the result of running locate a namespace for this using prefix.
1808
0
    return locate_a_namespace(prefix);
1809
0
}
1810
1811
// https://dom.spec.whatwg.org/#dom-node-lookupprefix
1812
Optional<String> Node::lookup_prefix(Optional<String> namespace_) const
1813
0
{
1814
    // 1. If namespace is null or the empty string, then return null.
1815
0
    if (!namespace_.has_value() || namespace_->is_empty())
1816
0
        return {};
1817
1818
    // 2. Switch on the interface this implements:
1819
1820
    // Element
1821
0
    if (is<Element>(*this)) {
1822
        // Return the result of locating a namespace prefix for it using namespace.
1823
0
        auto& element = verify_cast<Element>(*this);
1824
0
        return element.locate_a_namespace_prefix(namespace_);
1825
0
    }
1826
1827
    // Document
1828
0
    if (is<Document>(*this)) {
1829
        // Return the result of locating a namespace prefix for its document element, if its document element is non-null; otherwise null.
1830
0
        auto* document_element = verify_cast<Document>(*this).document_element();
1831
0
        if (!document_element)
1832
0
            return {};
1833
1834
0
        return document_element->locate_a_namespace_prefix(namespace_);
1835
0
    }
1836
1837
    // DocumentType
1838
    // DocumentFragment
1839
0
    if (is<DocumentType>(*this) || is<DocumentFragment>(*this))
1840
        // Return null
1841
0
        return {};
1842
1843
    // Attr
1844
0
    if (is<Attr>(*this)) {
1845
        // Return the result of locating a namespace prefix for its element, if its element is non-null; otherwise null.
1846
0
        auto* element = verify_cast<Attr>(*this).owner_element();
1847
0
        if (!element)
1848
0
            return {};
1849
1850
0
        return element->locate_a_namespace_prefix(namespace_);
1851
0
    }
1852
1853
    // Otherwise
1854
    // Return the result of locating a namespace prefix for its parent element, if its parent element is non-null; otherwise null.
1855
0
    auto* parent_element = this->parent_element();
1856
0
    if (!parent_element)
1857
0
        return {};
1858
1859
0
    return parent_element->locate_a_namespace_prefix(namespace_);
1860
0
}
1861
1862
// https://dom.spec.whatwg.org/#dom-node-isdefaultnamespace
1863
bool Node::is_default_namespace(Optional<String> namespace_) const
1864
0
{
1865
    // 1. If namespace is the empty string, then set it to null.
1866
0
    if (namespace_.has_value() && namespace_->is_empty())
1867
0
        namespace_ = {};
1868
1869
    // 2. Let defaultNamespace be the result of running locate a namespace for this using null.
1870
0
    auto default_namespace = locate_a_namespace({});
1871
1872
    // 3. Return true if defaultNamespace is the same as namespace; otherwise false.
1873
0
    return default_namespace == namespace_;
1874
0
}
1875
1876
// https://dom.spec.whatwg.org/#in-a-document-tree
1877
bool Node::in_a_document_tree() const
1878
0
{
1879
    // An element is in a document tree if its root is a document.
1880
0
    return root().is_document();
1881
0
}
1882
1883
// https://dom.spec.whatwg.org/#dom-node-getrootnode
1884
JS::NonnullGCPtr<Node> Node::get_root_node(GetRootNodeOptions const& options)
1885
0
{
1886
    // The getRootNode(options) method steps are to return this’s shadow-including root if options["composed"] is true;
1887
0
    if (options.composed)
1888
0
        return shadow_including_root();
1889
1890
    // otherwise this’s root.
1891
0
    return root();
1892
0
}
1893
1894
String Node::debug_description() const
1895
0
{
1896
0
    StringBuilder builder;
1897
0
    builder.append(node_name().to_deprecated_fly_string().to_lowercase());
1898
0
    if (is_element()) {
1899
0
        auto const& element = static_cast<DOM::Element const&>(*this);
1900
0
        if (element.id().has_value())
1901
0
            builder.appendff("#{}", element.id().value());
1902
0
        for (auto const& class_name : element.class_names())
1903
0
            builder.appendff(".{}", class_name);
1904
0
    }
1905
0
    return MUST(builder.to_string());
1906
0
}
1907
1908
// https://dom.spec.whatwg.org/#concept-node-length
1909
size_t Node::length() const
1910
0
{
1911
    // 1. If node is a DocumentType or Attr node, then return 0.
1912
0
    if (is_document_type() || is_attribute())
1913
0
        return 0;
1914
1915
    // 2. If node is a CharacterData node, then return node’s data’s length.
1916
0
    if (is_character_data())
1917
0
        return verify_cast<CharacterData>(*this).length_in_utf16_code_units();
1918
1919
    // 3. Return the number of node’s children.
1920
0
    return child_count();
1921
0
}
1922
1923
void Node::set_paintable(JS::GCPtr<Painting::Paintable> paintable)
1924
0
{
1925
0
    m_paintable = paintable;
1926
0
}
1927
1928
Painting::Paintable const* Node::paintable() const
1929
0
{
1930
0
    return m_paintable;
1931
0
}
1932
1933
Painting::Paintable* Node::paintable()
1934
0
{
1935
0
    return m_paintable;
1936
0
}
1937
1938
Painting::PaintableBox const* Node::paintable_box() const
1939
0
{
1940
0
    if (paintable() && paintable()->is_paintable_box())
1941
0
        return static_cast<Painting::PaintableBox const*>(paintable());
1942
0
    return nullptr;
1943
0
}
1944
1945
Painting::PaintableBox* Node::paintable_box()
1946
0
{
1947
0
    if (paintable() && paintable()->is_paintable_box())
1948
0
        return static_cast<Painting::PaintableBox*>(paintable());
1949
0
    return nullptr;
1950
0
}
1951
1952
// https://dom.spec.whatwg.org/#queue-a-mutation-record
1953
void Node::queue_mutation_record(FlyString const& type, Optional<FlyString> const& attribute_name, Optional<FlyString> const& attribute_namespace, Optional<String> const& old_value, Vector<JS::Handle<Node>> added_nodes, Vector<JS::Handle<Node>> removed_nodes, Node* previous_sibling, Node* next_sibling) const
1954
0
{
1955
    // NOTE: We defer garbage collection until the end of the scope, since we can't safely use MutationObserver* as a hashmap key otherwise.
1956
    // FIXME: This is a total hack.
1957
0
    JS::DeferGC defer_gc(heap());
1958
1959
    // 1. Let interestedObservers be an empty map.
1960
    // mutationObserver -> mappedOldValue
1961
0
    OrderedHashMap<MutationObserver*, Optional<String>> interested_observers;
1962
1963
    // 2. Let nodes be the inclusive ancestors of target.
1964
    // 3. For each node in nodes, and then for each registered of node’s registered observer list:
1965
0
    for (auto* node = this; node; node = node->parent()) {
1966
0
        if (!node->m_registered_observer_list)
1967
0
            continue;
1968
0
        for (auto& registered_observer : *node->m_registered_observer_list) {
1969
            // 1. Let options be registered’s options.
1970
0
            auto& options = registered_observer->options();
1971
1972
            // 2. If none of the following are true
1973
            //      - node is not target and options["subtree"] is false
1974
            //      - type is "attributes" and options["attributes"] either does not exist or is false
1975
            //      - type is "attributes", options["attributeFilter"] exists, and options["attributeFilter"] does not contain name or namespace is non-null
1976
            //      - type is "characterData" and options["characterData"] either does not exist or is false
1977
            //      - type is "childList" and options["childList"] is false
1978
            //    then:
1979
0
            if (!(node != this && !options.subtree)
1980
0
                && !(type == MutationType::attributes && (!options.attributes.has_value() || !options.attributes.value()))
1981
0
                && !(type == MutationType::attributes && options.attribute_filter.has_value() && (attribute_namespace.has_value() || !options.attribute_filter->contains_slow(attribute_name.value_or(String {}))))
1982
0
                && !(type == MutationType::characterData && (!options.character_data.has_value() || !options.character_data.value()))
1983
0
                && !(type == MutationType::childList && !options.child_list)) {
1984
                // 1. Let mo be registered’s observer.
1985
0
                auto mutation_observer = registered_observer->observer();
1986
1987
                // 2. If interestedObservers[mo] does not exist, then set interestedObservers[mo] to null.
1988
0
                if (!interested_observers.contains(mutation_observer))
1989
0
                    interested_observers.set(mutation_observer, {});
1990
1991
                // 3. If either type is "attributes" and options["attributeOldValue"] is true, or type is "characterData" and options["characterDataOldValue"] is true, then set interestedObservers[mo] to oldValue.
1992
0
                if ((type == MutationType::attributes && options.attribute_old_value.has_value() && options.attribute_old_value.value()) || (type == MutationType::characterData && options.character_data_old_value.has_value() && options.character_data_old_value.value()))
1993
0
                    interested_observers.set(mutation_observer, old_value);
1994
0
            }
1995
0
        }
1996
0
    }
1997
1998
    // OPTIMIZATION: If there are no interested observers, bail without doing any more work.
1999
0
    if (interested_observers.is_empty())
2000
0
        return;
2001
2002
0
    auto added_nodes_list = StaticNodeList::create(realm(), move(added_nodes));
2003
0
    auto removed_nodes_list = StaticNodeList::create(realm(), move(removed_nodes));
2004
2005
    // 4. For each observer → mappedOldValue of interestedObservers:
2006
0
    for (auto& interested_observer : interested_observers) {
2007
        // FIXME: The MutationRecord constructor shuld take an Optional<FlyString> attribute name and namespace
2008
0
        Optional<String> string_attribute_name;
2009
0
        if (attribute_name.has_value())
2010
0
            string_attribute_name = attribute_name->to_string();
2011
0
        Optional<String> string_attribute_namespace;
2012
0
        if (attribute_namespace.has_value())
2013
0
            string_attribute_name = attribute_namespace->to_string();
2014
2015
        // 1. Let record be a new MutationRecord object with its type set to type, target set to target, attributeName set to name, attributeNamespace set to namespace, oldValue set to mappedOldValue,
2016
        //    addedNodes set to addedNodes, removedNodes set to removedNodes, previousSibling set to previousSibling, and nextSibling set to nextSibling.
2017
0
        auto record = MutationRecord::create(realm(), type, *this, added_nodes_list, removed_nodes_list, previous_sibling, next_sibling, string_attribute_name, string_attribute_namespace, /* mappedOldValue */ interested_observer.value);
2018
2019
        // 2. Enqueue record to observer’s record queue.
2020
0
        interested_observer.key->enqueue_record({}, move(record));
2021
0
    }
2022
2023
    // 5. Queue a mutation observer microtask.
2024
0
    Bindings::queue_mutation_observer_microtask(document());
2025
0
}
2026
2027
// https://dom.spec.whatwg.org/#queue-a-tree-mutation-record
2028
void Node::queue_tree_mutation_record(Vector<JS::Handle<Node>> added_nodes, Vector<JS::Handle<Node>> removed_nodes, Node* previous_sibling, Node* next_sibling)
2029
0
{
2030
    // 1. Assert: either addedNodes or removedNodes is not empty.
2031
0
    VERIFY(added_nodes.size() > 0 || removed_nodes.size() > 0);
2032
2033
    // 2. Queue a mutation record of "childList" for target with null, null, null, addedNodes, removedNodes, previousSibling, and nextSibling.
2034
0
    queue_mutation_record(MutationType::childList, {}, {}, {}, move(added_nodes), move(removed_nodes), previous_sibling, next_sibling);
2035
0
}
2036
2037
void Node::append_child_impl(JS::NonnullGCPtr<Node> node)
2038
0
{
2039
0
    VERIFY(!node->m_parent);
2040
2041
0
    if (!is_child_allowed(*node))
2042
0
        return;
2043
2044
0
    if (m_last_child)
2045
0
        m_last_child->m_next_sibling = node.ptr();
2046
0
    node->m_previous_sibling = m_last_child;
2047
0
    node->m_parent = this;
2048
0
    m_last_child = node.ptr();
2049
0
    if (!m_first_child)
2050
0
        m_first_child = m_last_child;
2051
0
}
2052
2053
void Node::insert_before_impl(JS::NonnullGCPtr<Node> node, JS::GCPtr<Node> child)
2054
0
{
2055
0
    if (!child)
2056
0
        return append_child_impl(move(node));
2057
2058
0
    VERIFY(!node->m_parent);
2059
0
    VERIFY(child->parent() == this);
2060
2061
0
    node->m_previous_sibling = child->m_previous_sibling;
2062
0
    node->m_next_sibling = child;
2063
2064
0
    if (child->m_previous_sibling)
2065
0
        child->m_previous_sibling->m_next_sibling = node;
2066
2067
0
    if (m_first_child == child)
2068
0
        m_first_child = node;
2069
2070
0
    child->m_previous_sibling = node;
2071
2072
0
    node->m_parent = this;
2073
0
}
2074
2075
void Node::remove_child_impl(JS::NonnullGCPtr<Node> node)
2076
0
{
2077
0
    VERIFY(node->m_parent.ptr() == this);
2078
2079
0
    if (m_first_child == node)
2080
0
        m_first_child = node->m_next_sibling;
2081
2082
0
    if (m_last_child == node)
2083
0
        m_last_child = node->m_previous_sibling;
2084
2085
0
    if (node->m_next_sibling)
2086
0
        node->m_next_sibling->m_previous_sibling = node->m_previous_sibling;
2087
2088
0
    if (node->m_previous_sibling)
2089
0
        node->m_previous_sibling->m_next_sibling = node->m_next_sibling;
2090
2091
0
    node->m_next_sibling = nullptr;
2092
0
    node->m_previous_sibling = nullptr;
2093
0
    node->m_parent = nullptr;
2094
0
}
2095
2096
bool Node::is_ancestor_of(Node const& other) const
2097
0
{
2098
0
    for (auto* ancestor = other.parent(); ancestor; ancestor = ancestor->parent()) {
2099
0
        if (ancestor == this)
2100
0
            return true;
2101
0
    }
2102
0
    return false;
2103
0
}
2104
2105
bool Node::is_inclusive_ancestor_of(Node const& other) const
2106
0
{
2107
0
    return &other == this || is_ancestor_of(other);
2108
0
}
2109
2110
bool Node::is_descendant_of(Node const& other) const
2111
0
{
2112
0
    return other.is_ancestor_of(*this);
2113
0
}
2114
2115
bool Node::is_inclusive_descendant_of(Node const& other) const
2116
0
{
2117
0
    return other.is_inclusive_ancestor_of(*this);
2118
0
}
2119
2120
// https://dom.spec.whatwg.org/#concept-tree-following
2121
bool Node::is_following(Node const& other) const
2122
0
{
2123
    // An object A is following an object B if A and B are in the same tree and A comes after B in tree order.
2124
0
    for (auto* node = previous_in_pre_order(); node; node = node->previous_in_pre_order()) {
2125
0
        if (node == &other)
2126
0
            return true;
2127
0
    }
2128
2129
0
    return false;
2130
0
}
2131
2132
void Node::build_accessibility_tree(AccessibilityTreeNode& parent)
2133
0
{
2134
0
    if (is_uninteresting_whitespace_node())
2135
0
        return;
2136
2137
0
    if (is_document()) {
2138
0
        auto* document = static_cast<DOM::Document*>(this);
2139
0
        auto* document_element = document->document_element();
2140
0
        if (document_element && document_element->include_in_accessibility_tree()) {
2141
0
            parent.set_value(document_element);
2142
0
            if (document_element->has_child_nodes())
2143
0
                document_element->for_each_child([&parent](DOM::Node& child) {
2144
0
                    child.build_accessibility_tree(parent);
2145
0
                    return IterationDecision::Continue;
2146
0
                });
2147
0
        }
2148
0
    } else if (is_element()) {
2149
0
        auto const* element = static_cast<DOM::Element const*>(this);
2150
2151
0
        if (is<HTML::HTMLScriptElement>(element) || is<HTML::HTMLStyleElement>(element))
2152
0
            return;
2153
2154
0
        if (element->include_in_accessibility_tree()) {
2155
0
            auto current_node = AccessibilityTreeNode::create(&document(), this);
2156
0
            parent.append_child(current_node);
2157
0
            if (has_child_nodes()) {
2158
0
                for_each_child([&current_node](DOM::Node& child) {
2159
0
                    child.build_accessibility_tree(*current_node);
2160
0
                    return IterationDecision::Continue;
2161
0
                });
2162
0
            }
2163
0
        } else if (has_child_nodes()) {
2164
0
            for_each_child([&parent](DOM::Node& child) {
2165
0
                child.build_accessibility_tree(parent);
2166
0
                return IterationDecision::Continue;
2167
0
            });
2168
0
        }
2169
0
    } else if (is_text()) {
2170
0
        parent.append_child(AccessibilityTreeNode::create(&document(), this));
2171
0
        if (has_child_nodes()) {
2172
0
            for_each_child([&parent](DOM::Node& child) {
2173
0
                child.build_accessibility_tree(parent);
2174
0
                return IterationDecision::Continue;
2175
0
            });
2176
0
        }
2177
0
    }
2178
0
}
2179
2180
// https://www.w3.org/TR/accname-1.2/#mapping_additional_nd_te
2181
ErrorOr<String> Node::name_or_description(NameOrDescription target, Document const& document, HashTable<i32>& visited_nodes) const
2182
0
{
2183
    // The text alternative for a given element is computed as follows:
2184
    // 1. Set the root node to the given element, the current node to the root node, and the total accumulated text to the empty string (""). If the root node's role prohibits naming, return the empty string ("").
2185
0
    auto const* root_node = this;
2186
0
    auto const* current_node = root_node;
2187
0
    StringBuilder total_accumulated_text;
2188
0
    visited_nodes.set(unique_id());
2189
2190
0
    if (is_element()) {
2191
0
        auto const* element = static_cast<DOM::Element const*>(this);
2192
        // 2. Compute the text alternative for the current node:
2193
        // A. If the current node is hidden and is not directly referenced by aria-labelledby or aria-describedby, nor directly referenced by a native host language text alternative element (e.g. label in HTML) or attribute, return the empty string.
2194
        // FIXME: Check for references
2195
0
        if (element->aria_hidden() == "true" || !layout_node())
2196
0
            return String {};
2197
        // B. Otherwise:
2198
        // - if computing a name, and the current node has an aria-labelledby attribute that contains at least one valid IDREF, and the current node is not already part of an aria-labelledby traversal,
2199
        //   process its IDREFs in the order they occur:
2200
        // - or, if computing a description, and the current node has an aria-describedby attribute that contains at least one valid IDREF, and the current node is not already part of an aria-describedby traversal,
2201
        //   process its IDREFs in the order they occur:
2202
0
        auto aria_labelled_by = element->aria_labelled_by();
2203
0
        auto aria_described_by = element->aria_described_by();
2204
0
        if ((target == NameOrDescription::Name && aria_labelled_by.has_value() && Node::first_valid_id(*aria_labelled_by, document).has_value())
2205
0
            || (target == NameOrDescription::Description && aria_described_by.has_value() && Node::first_valid_id(*aria_described_by, document).has_value())) {
2206
2207
            // i. Set the accumulated text to the empty string.
2208
0
            total_accumulated_text.clear();
2209
2210
0
            Vector<StringView> id_list;
2211
0
            if (target == NameOrDescription::Name) {
2212
0
                id_list = aria_labelled_by->bytes_as_string_view().split_view_if(Infra::is_ascii_whitespace);
2213
0
            } else {
2214
0
                id_list = aria_described_by->bytes_as_string_view().split_view_if(Infra::is_ascii_whitespace);
2215
0
            }
2216
            // ii. For each IDREF:
2217
0
            for (auto const& id_ref : id_list) {
2218
0
                auto node = document.get_element_by_id(MUST(FlyString::from_utf8(id_ref)));
2219
0
                if (!node)
2220
0
                    continue;
2221
2222
0
                if (visited_nodes.contains(node->unique_id()))
2223
0
                    continue;
2224
                // a. Set the current node to the node referenced by the IDREF.
2225
0
                current_node = node;
2226
                // b. Compute the text alternative of the current node beginning with step 2. Set the result to that text alternative.
2227
0
                auto result = TRY(node->name_or_description(target, document, visited_nodes));
2228
                // c. Append the result, with a space, to the accumulated text.
2229
0
                TRY(Node::append_with_space(total_accumulated_text, result));
2230
0
            }
2231
            // iii. Return the accumulated text.
2232
0
            return total_accumulated_text.to_string();
2233
0
        }
2234
        // C. Otherwise, if computing a name, and if the current node has an aria-label attribute whose value is not the empty string, nor, when trimmed of white space, is not the empty string:
2235
0
        if (target == NameOrDescription::Name && element->aria_label().has_value() && !element->aria_label()->is_empty() && !element->aria_label()->bytes_as_string_view().is_whitespace()) {
2236
            // TODO: - If traversal of the current node is due to recursion and the current node is an embedded control as defined in step 2E, ignore aria-label and skip to rule 2E.
2237
            // - Otherwise, return the value of aria-label.
2238
0
            return element->aria_label().value();
2239
0
        }
2240
        // TODO: D. Otherwise, if the current node's native markup provides an attribute (e.g. title) or element (e.g. HTML label) that defines a text alternative,
2241
        //      return that alternative in the form of a flat string as defined by the host language, unless the element is marked as presentational (role="presentation" or role="none").
2242
2243
        // TODO: E. Otherwise, if the current node is a control embedded within the label (e.g. the label element in HTML or any element directly referenced by aria-labelledby) for another widget, where the user can adjust the embedded
2244
        //          control's value, then include the embedded control as part of the text alternative in the following manner:
2245
        //   - If the embedded control has role textbox, return its value.
2246
        //   - If the embedded control has role menu button, return the text alternative of the button.
2247
        //   - If the embedded control has role combobox or listbox, return the text alternative of the chosen option.
2248
        //   - If the embedded control has role range (e.g., a spinbutton or slider):
2249
        //      - If the aria-valuetext property is present, return its value,
2250
        //      - Otherwise, if the aria-valuenow property is present, return its value,
2251
        //      - Otherwise, use the value as specified by a host language attribute.
2252
2253
        // F. Otherwise, if the current node's role allows name from content, or if the current node is referenced by aria-labelledby, aria-describedby, or is a native host language text alternative element (e.g. label in HTML), or is a descendant of a native host language text alternative element:
2254
0
        auto role = element->role_or_default();
2255
0
        if (role.has_value() && ARIA::allows_name_from_content(role.value())) {
2256
            // i. Set the accumulated text to the empty string.
2257
0
            total_accumulated_text.clear();
2258
            // ii. Check for CSS generated textual content associated with the current node and include it in the accumulated text. The CSS :before and :after pseudo elements [CSS2] can provide textual content for elements that have a content model.
2259
0
            auto before = element->get_pseudo_element_node(CSS::Selector::PseudoElement::Type::Before);
2260
0
            auto after = element->get_pseudo_element_node(CSS::Selector::PseudoElement::Type::After);
2261
            // - For :before pseudo elements, User agents MUST prepend CSS textual content, without a space, to the textual content of the current node.
2262
0
            if (before)
2263
0
                TRY(Node::prepend_without_space(total_accumulated_text, before->computed_values().content().data));
2264
2265
            // - For :after pseudo elements, User agents MUST append CSS textual content, without a space, to the textual content of the current node.
2266
0
            if (after)
2267
0
                TRY(Node::append_without_space(total_accumulated_text, after->computed_values().content().data));
2268
2269
            // iii. For each child node of the current node:
2270
0
            element->for_each_child([&total_accumulated_text, current_node, target, &document, &visited_nodes](
2271
0
                                        DOM::Node const& child_node) mutable {
2272
0
                if (!child_node.is_element() && !child_node.is_text())
2273
0
                    return IterationDecision::Continue;
2274
0
                bool should_add_space = true;
2275
0
                const_cast<DOM::Document&>(document).update_layout();
2276
0
                auto const* layout_node = child_node.layout_node();
2277
0
                if (layout_node) {
2278
0
                    auto display = layout_node->display();
2279
0
                    if (display.is_inline_outside() && display.is_flow_inside()) {
2280
0
                        should_add_space = false;
2281
0
                    }
2282
0
                }
2283
2284
0
                if (visited_nodes.contains(child_node.unique_id()))
2285
0
                    return IterationDecision::Continue;
2286
2287
                // a. Set the current node to the child node.
2288
0
                current_node = &child_node;
2289
2290
                // b. Compute the text alternative of the current node beginning with step 2. Set the result to that text alternative.
2291
0
                auto result = MUST(current_node->name_or_description(target, document, visited_nodes));
2292
2293
                // Append a space character and the result of each step above to the total accumulated text.
2294
                // AD-HOC: Doing the space-adding here is in a different order from what the spec states.
2295
0
                if (should_add_space)
2296
0
                    total_accumulated_text.append(' ');
2297
                // c. Append the result to the accumulated text.
2298
0
                total_accumulated_text.append(result);
2299
2300
0
                return IterationDecision::Continue;
2301
0
            });
2302
            // iv. Return the accumulated text.
2303
0
            return total_accumulated_text.to_string();
2304
            // Important: Each node in the subtree is consulted only once. If text has been collected from a descendant, but is referenced by another IDREF in some descendant node, then that second, or subsequent, reference is not followed. This is done to avoid infinite loops.
2305
0
        }
2306
0
    }
2307
2308
    // G. Text Node: Otherwise, if the current node is a Text Node, return its textual contents.
2309
0
    if (is_text()) {
2310
0
        if (layout_node() && layout_node()->is_text_node())
2311
0
            return verify_cast<Layout::TextNode>(layout_node())->text_for_rendering();
2312
0
        return text_content().value();
2313
0
    }
2314
2315
    // TODO: H. Otherwise, if the current node is a descendant of an element whose Accessible Name or Accessible Description is being computed, and contains descendants, proceed to 2F.i.
2316
2317
    // I. Otherwise, if the current node has a Tooltip attribute, return its value.
2318
    // https://www.w3.org/TR/accname-1.2/#dfn-tooltip-attribute
2319
    // Any host language attribute that would result in a user agent generating a tooltip such as in response to a mouse hover in desktop user agents.
2320
    // FIXME: Support SVG tooltips and CSS tooltips
2321
0
    if (is<HTML::HTMLElement>(this)) {
2322
0
        auto const* element = static_cast<HTML::HTMLElement const*>(this);
2323
0
        auto tooltip = element->title();
2324
0
        if (tooltip.has_value() && !tooltip->is_empty())
2325
0
            return tooltip.release_value();
2326
0
    }
2327
    // After all steps are completed, the total accumulated text is used as the accessible name or accessible description of the element that initiated the computation.
2328
0
    return total_accumulated_text.to_string();
2329
0
}
2330
2331
// https://www.w3.org/TR/accname-1.2/#mapping_additional_nd_name
2332
ErrorOr<String> Node::accessible_name(Document const& document) const
2333
0
{
2334
0
    HashTable<i32> visited_nodes;
2335
    // User agents MUST compute an accessible name using the rules outlined below in the section titled Accessible Name and Description Computation.
2336
0
    return name_or_description(NameOrDescription::Name, document, visited_nodes);
2337
0
}
2338
2339
// https://www.w3.org/TR/accname-1.2/#mapping_additional_nd_description
2340
ErrorOr<String> Node::accessible_description(Document const& document) const
2341
0
{
2342
    // If aria-describedby is present, user agents MUST compute the accessible description by concatenating the text alternatives for elements referenced by an aria-describedby attribute on the current element.
2343
    // The text alternatives for the referenced elements are computed using a number of methods, outlined below in the section titled Accessible Name and Description Computation.
2344
0
    if (!is_element())
2345
0
        return String {};
2346
2347
0
    auto const* element = static_cast<Element const*>(this);
2348
0
    auto described_by = element->aria_described_by();
2349
0
    if (!described_by.has_value())
2350
0
        return String {};
2351
2352
0
    HashTable<i32> visited_nodes;
2353
0
    StringBuilder builder;
2354
0
    auto id_list = described_by->bytes_as_string_view().split_view_if(Infra::is_ascii_whitespace);
2355
0
    for (auto const& id : id_list) {
2356
0
        if (auto description_element = document.get_element_by_id(MUST(FlyString::from_utf8(id)))) {
2357
0
            auto description = TRY(
2358
0
                description_element->name_or_description(NameOrDescription::Description, document,
2359
0
                    visited_nodes));
2360
0
            if (!description.is_empty()) {
2361
0
                if (builder.is_empty()) {
2362
0
                    builder.append(description);
2363
0
                } else {
2364
0
                    builder.append(" "sv);
2365
0
                    builder.append(description);
2366
0
                }
2367
0
            }
2368
0
        }
2369
0
    }
2370
0
    return builder.to_string();
2371
0
}
2372
2373
Optional<StringView> Node::first_valid_id(StringView value, Document const& document)
2374
0
{
2375
0
    auto id_list = value.split_view_if(Infra::is_ascii_whitespace);
2376
0
    for (auto const& id : id_list) {
2377
0
        if (document.get_element_by_id(MUST(FlyString::from_utf8(id))))
2378
0
            return id;
2379
0
    }
2380
0
    return {};
2381
0
}
2382
2383
// https://www.w3.org/TR/accname-1.2/#mapping_additional_nd_te
2384
ErrorOr<void> Node::append_without_space(StringBuilder x, StringView const& result)
2385
0
{
2386
    // - If X is empty, copy the result to X.
2387
    // - If X is non-empty, copy the result to the end of X.
2388
0
    TRY(x.try_append(result));
2389
0
    return {};
2390
0
}
2391
2392
// https://www.w3.org/TR/accname-1.2/#mapping_additional_nd_te
2393
ErrorOr<void> Node::append_with_space(StringBuilder x, StringView const& result)
2394
0
{
2395
    // - If X is empty, copy the result to X.
2396
0
    if (x.is_empty()) {
2397
0
        TRY(x.try_append(result));
2398
0
    } else {
2399
        // - If X is non-empty, add a space to the end of X and then copy the result to X after the space.
2400
0
        TRY(x.try_append(" "sv));
2401
0
        TRY(x.try_append(result));
2402
0
    }
2403
0
    return {};
2404
0
}
2405
2406
// https://www.w3.org/TR/accname-1.2/#mapping_additional_nd_te
2407
ErrorOr<void> Node::prepend_without_space(StringBuilder x, StringView const& result)
2408
0
{
2409
    // - If X is empty, copy the result to X.
2410
0
    if (x.is_empty()) {
2411
0
        x.append(result);
2412
0
    } else {
2413
        // - If X is non-empty, copy the result to the start of X.
2414
0
        auto temp = TRY(x.to_string());
2415
0
        x.clear();
2416
0
        TRY(x.try_append(result));
2417
0
        TRY(x.try_append(temp));
2418
0
    }
2419
0
    return {};
2420
0
}
2421
2422
// https://www.w3.org/TR/accname-1.2/#mapping_additional_nd_te
2423
ErrorOr<void> Node::prepend_with_space(StringBuilder x, StringView const& result)
2424
0
{
2425
    // - If X is empty, copy the result to X.
2426
0
    if (x.is_empty()) {
2427
0
        TRY(x.try_append(result));
2428
0
    } else {
2429
        // - If X is non-empty, copy the result to the start of X, and add a space after the copy.
2430
0
        auto temp = TRY(x.to_string());
2431
0
        x.clear();
2432
0
        TRY(x.try_append(result));
2433
0
        TRY(x.try_append(" "sv));
2434
0
        TRY(x.try_append(temp));
2435
0
    }
2436
0
    return {};
2437
0
}
2438
2439
void Node::add_registered_observer(RegisteredObserver& registered_observer)
2440
0
{
2441
0
    if (!m_registered_observer_list)
2442
0
        m_registered_observer_list = make<Vector<JS::NonnullGCPtr<RegisteredObserver>>>();
2443
0
    m_registered_observer_list->append(registered_observer);
2444
0
}
2445
2446
}