summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb/DOM/MutationObserver.cpp
blob: 5d587e246f95974bd9534a96813b9277c097481c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/*
 * Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/DOM/MutationObserver.h>
#include <LibWeb/DOM/Node.h>

namespace Web::DOM {

WebIDL::ExceptionOr<JS::NonnullGCPtr<MutationObserver>> MutationObserver::construct_impl(JS::Realm& realm, JS::GCPtr<WebIDL::CallbackType> callback)
{
    return MUST_OR_THROW_OOM(realm.heap().allocate<MutationObserver>(realm, realm, callback));
}

// https://dom.spec.whatwg.org/#dom-mutationobserver-mutationobserver
MutationObserver::MutationObserver(JS::Realm& realm, JS::GCPtr<WebIDL::CallbackType> callback)
    : PlatformObject(realm)
    , m_callback(move(callback))
{

    // 1. Set this’s callback to callback.

    // 2. Append this to this’s relevant agent’s mutation observers.
    auto* agent_custom_data = verify_cast<Bindings::WebEngineCustomData>(realm.vm().custom_data());
    agent_custom_data->mutation_observers.append(*this);
}

MutationObserver::~MutationObserver() = default;

JS::ThrowCompletionOr<void> MutationObserver::initialize(JS::Realm& realm)
{
    MUST_OR_THROW_OOM(Base::initialize(realm));
    set_prototype(&Bindings::ensure_web_prototype<Bindings::MutationObserverPrototype>(realm, "MutationObserver"));

    return {};
}

void MutationObserver::visit_edges(Cell::Visitor& visitor)
{
    Base::visit_edges(visitor);
    visitor.visit(m_callback.ptr());
    for (auto& record : m_record_queue)
        visitor.visit(record.ptr());
}

// https://dom.spec.whatwg.org/#dom-mutationobserver-observe
WebIDL::ExceptionOr<void> MutationObserver::observe(Node& target, MutationObserverInit options)
{
    // 1. If either options["attributeOldValue"] or options["attributeFilter"] exists, and options["attributes"] does not exist, then set options["attributes"] to true.
    if ((options.attribute_old_value.has_value() || options.attribute_filter.has_value()) && !options.attributes.has_value())
        options.attributes = true;

    // 2. If options["characterDataOldValue"] exists and options["characterData"] does not exist, then set options["characterData"] to true.
    if (options.character_data_old_value.has_value() && !options.character_data.has_value())
        options.character_data = true;

    // 3. If none of options["childList"], options["attributes"], and options["characterData"] is true, then throw a TypeError.
    if (!options.child_list && (!options.attributes.has_value() || !options.attributes.value()) && (!options.character_data.has_value() || !options.character_data.value()))
        return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Options must have one of childList, attributes or characterData set to true."sv };

    // 4. If options["attributeOldValue"] is true and options["attributes"] is false, then throw a TypeError.
    // NOTE: If attributeOldValue is present, attributes will be present because of step 1.
    if (options.attribute_old_value.has_value() && options.attribute_old_value.value() && !options.attributes.value())
        return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "attributes must be true if attributeOldValue is true."sv };

    // 5. If options["attributeFilter"] is present and options["attributes"] is false, then throw a TypeError.
    // NOTE: If attributeFilter is present, attributes will be present because of step 1.
    if (options.attribute_filter.has_value() && !options.attributes.value())
        return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "attributes must be true if attributeFilter is present."sv };

    // 6. If options["characterDataOldValue"] is true and options["characterData"] is false, then throw a TypeError.
    // NOTE: If characterDataOldValue is present, characterData will be present because of step 2.
    if (options.character_data_old_value.has_value() && options.character_data_old_value.value() && !options.character_data.value())
        return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "characterData must be true if characterDataOldValue is true."sv };

    // 7. For each registered of target’s registered observer list, if registered’s observer is this:
    bool updated_existing_observer = false;
    for (auto& registered_observer : target.registered_observers_list()) {
        if (registered_observer.observer().ptr() != this)
            continue;

        updated_existing_observer = true;

        // 1. For each node of this’s node list, remove all transient registered observers whose source is registered from node’s registered observer list.
        for (auto& node : m_node_list) {
            // FIXME: Is this correct?
            if (node.is_null())
                continue;

            node->registered_observers_list().remove_all_matching([&registered_observer](RegisteredObserver& observer) {
                return is<TransientRegisteredObserver>(observer) && verify_cast<TransientRegisteredObserver>(observer).source().ptr() == &registered_observer;
            });
        }

        // 2. Set registered’s options to options.
        registered_observer.set_options(options);
        break;
    }

    // 8. Otherwise:
    if (!updated_existing_observer) {
        // 1. Append a new registered observer whose observer is this and options is options to target’s registered observer list.
        auto new_registered_observer = RegisteredObserver::create(*this, options);
        target.add_registered_observer(new_registered_observer);

        // 2. Append target to this’s node list.
        m_node_list.append(target.make_weak_ptr<Node>());
    }

    return {};
}

// https://dom.spec.whatwg.org/#dom-mutationobserver-disconnect
void MutationObserver::disconnect()
{
    // 1. For each node of this’s node list, remove any registered observer from node’s registered observer list for which this is the observer.
    for (auto& node : m_node_list) {
        // FIXME: Is this correct?
        if (node.is_null())
            continue;

        node->registered_observers_list().remove_all_matching([this](RegisteredObserver& registered_observer) {
            return registered_observer.observer().ptr() == this;
        });
    }

    // 2. Empty this’s record queue.
    m_record_queue.clear();
}

// https://dom.spec.whatwg.org/#dom-mutationobserver-takerecords
Vector<JS::Handle<MutationRecord>> MutationObserver::take_records()
{
    // 1. Let records be a clone of this’s record queue.
    Vector<JS::Handle<MutationRecord>> records;
    for (auto& record : m_record_queue)
        records.append(*record);

    // 2. Empty this’s record queue.
    m_record_queue.clear();

    // 3. Return records.
    return records;
}

JS::NonnullGCPtr<RegisteredObserver> RegisteredObserver::create(MutationObserver& observer, MutationObserverInit const& options)
{
    return observer.heap().allocate_without_realm<RegisteredObserver>(observer, options);
}

RegisteredObserver::RegisteredObserver(MutationObserver& observer, MutationObserverInit const& options)
    : m_observer(observer)
    , m_options(options)
{
}

RegisteredObserver::~RegisteredObserver() = default;

void RegisteredObserver::visit_edges(Cell::Visitor& visitor)
{
    Base::visit_edges(visitor);
    visitor.visit(m_observer.ptr());
}

JS::NonnullGCPtr<TransientRegisteredObserver> TransientRegisteredObserver::create(MutationObserver& observer, MutationObserverInit const& options, RegisteredObserver& source)
{
    return observer.heap().allocate_without_realm<TransientRegisteredObserver>(observer, options, source);
}

TransientRegisteredObserver::TransientRegisteredObserver(MutationObserver& observer, MutationObserverInit const& options, RegisteredObserver& source)
    : RegisteredObserver(observer, options)
    , m_source(source)
{
}

TransientRegisteredObserver::~TransientRegisteredObserver() = default;

void TransientRegisteredObserver::visit_edges(Cell::Visitor& visitor)
{
    Base::visit_edges(visitor);
    visitor.visit(m_source.ptr());
}

}