Serenity Operating System
1/*
2 * Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <LibWeb/Bindings/Intrinsics.h>
8#include <LibWeb/Bindings/MainThreadVM.h>
9#include <LibWeb/DOM/MutationObserver.h>
10#include <LibWeb/DOM/Node.h>
11
12namespace Web::DOM {
13
14WebIDL::ExceptionOr<JS::NonnullGCPtr<MutationObserver>> MutationObserver::construct_impl(JS::Realm& realm, JS::GCPtr<WebIDL::CallbackType> callback)
15{
16 return MUST_OR_THROW_OOM(realm.heap().allocate<MutationObserver>(realm, realm, callback));
17}
18
19// https://dom.spec.whatwg.org/#dom-mutationobserver-mutationobserver
20MutationObserver::MutationObserver(JS::Realm& realm, JS::GCPtr<WebIDL::CallbackType> callback)
21 : PlatformObject(realm)
22 , m_callback(move(callback))
23{
24
25 // 1. Set this’s callback to callback.
26
27 // 2. Append this to this’s relevant agent’s mutation observers.
28 auto* agent_custom_data = verify_cast<Bindings::WebEngineCustomData>(realm.vm().custom_data());
29 agent_custom_data->mutation_observers.append(*this);
30}
31
32MutationObserver::~MutationObserver() = default;
33
34JS::ThrowCompletionOr<void> MutationObserver::initialize(JS::Realm& realm)
35{
36 MUST_OR_THROW_OOM(Base::initialize(realm));
37 set_prototype(&Bindings::ensure_web_prototype<Bindings::MutationObserverPrototype>(realm, "MutationObserver"));
38
39 return {};
40}
41
42void MutationObserver::visit_edges(Cell::Visitor& visitor)
43{
44 Base::visit_edges(visitor);
45 visitor.visit(m_callback.ptr());
46 for (auto& record : m_record_queue)
47 visitor.visit(record.ptr());
48}
49
50// https://dom.spec.whatwg.org/#dom-mutationobserver-observe
51WebIDL::ExceptionOr<void> MutationObserver::observe(Node& target, MutationObserverInit options)
52{
53 // 1. If either options["attributeOldValue"] or options["attributeFilter"] exists, and options["attributes"] does not exist, then set options["attributes"] to true.
54 if ((options.attribute_old_value.has_value() || options.attribute_filter.has_value()) && !options.attributes.has_value())
55 options.attributes = true;
56
57 // 2. If options["characterDataOldValue"] exists and options["characterData"] does not exist, then set options["characterData"] to true.
58 if (options.character_data_old_value.has_value() && !options.character_data.has_value())
59 options.character_data = true;
60
61 // 3. If none of options["childList"], options["attributes"], and options["characterData"] is true, then throw a TypeError.
62 if (!options.child_list && (!options.attributes.has_value() || !options.attributes.value()) && (!options.character_data.has_value() || !options.character_data.value()))
63 return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Options must have one of childList, attributes or characterData set to true."sv };
64
65 // 4. If options["attributeOldValue"] is true and options["attributes"] is false, then throw a TypeError.
66 // NOTE: If attributeOldValue is present, attributes will be present because of step 1.
67 if (options.attribute_old_value.has_value() && options.attribute_old_value.value() && !options.attributes.value())
68 return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "attributes must be true if attributeOldValue is true."sv };
69
70 // 5. If options["attributeFilter"] is present and options["attributes"] is false, then throw a TypeError.
71 // NOTE: If attributeFilter is present, attributes will be present because of step 1.
72 if (options.attribute_filter.has_value() && !options.attributes.value())
73 return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "attributes must be true if attributeFilter is present."sv };
74
75 // 6. If options["characterDataOldValue"] is true and options["characterData"] is false, then throw a TypeError.
76 // NOTE: If characterDataOldValue is present, characterData will be present because of step 2.
77 if (options.character_data_old_value.has_value() && options.character_data_old_value.value() && !options.character_data.value())
78 return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "characterData must be true if characterDataOldValue is true."sv };
79
80 // 7. For each registered of target’s registered observer list, if registered’s observer is this:
81 bool updated_existing_observer = false;
82 for (auto& registered_observer : target.registered_observers_list()) {
83 if (registered_observer.observer().ptr() != this)
84 continue;
85
86 updated_existing_observer = true;
87
88 // 1. For each node of this’s node list, remove all transient registered observers whose source is registered from node’s registered observer list.
89 for (auto& node : m_node_list) {
90 // FIXME: Is this correct?
91 if (node.is_null())
92 continue;
93
94 node->registered_observers_list().remove_all_matching([®istered_observer](RegisteredObserver& observer) {
95 return is<TransientRegisteredObserver>(observer) && verify_cast<TransientRegisteredObserver>(observer).source().ptr() == ®istered_observer;
96 });
97 }
98
99 // 2. Set registered’s options to options.
100 registered_observer.set_options(options);
101 break;
102 }
103
104 // 8. Otherwise:
105 if (!updated_existing_observer) {
106 // 1. Append a new registered observer whose observer is this and options is options to target’s registered observer list.
107 auto new_registered_observer = RegisteredObserver::create(*this, options);
108 target.add_registered_observer(new_registered_observer);
109
110 // 2. Append target to this’s node list.
111 m_node_list.append(target.make_weak_ptr<Node>());
112 }
113
114 return {};
115}
116
117// https://dom.spec.whatwg.org/#dom-mutationobserver-disconnect
118void MutationObserver::disconnect()
119{
120 // 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.
121 for (auto& node : m_node_list) {
122 // FIXME: Is this correct?
123 if (node.is_null())
124 continue;
125
126 node->registered_observers_list().remove_all_matching([this](RegisteredObserver& registered_observer) {
127 return registered_observer.observer().ptr() == this;
128 });
129 }
130
131 // 2. Empty this’s record queue.
132 m_record_queue.clear();
133}
134
135// https://dom.spec.whatwg.org/#dom-mutationobserver-takerecords
136Vector<JS::Handle<MutationRecord>> MutationObserver::take_records()
137{
138 // 1. Let records be a clone of this’s record queue.
139 Vector<JS::Handle<MutationRecord>> records;
140 for (auto& record : m_record_queue)
141 records.append(*record);
142
143 // 2. Empty this’s record queue.
144 m_record_queue.clear();
145
146 // 3. Return records.
147 return records;
148}
149
150JS::NonnullGCPtr<RegisteredObserver> RegisteredObserver::create(MutationObserver& observer, MutationObserverInit const& options)
151{
152 return observer.heap().allocate_without_realm<RegisteredObserver>(observer, options);
153}
154
155RegisteredObserver::RegisteredObserver(MutationObserver& observer, MutationObserverInit const& options)
156 : m_observer(observer)
157 , m_options(options)
158{
159}
160
161RegisteredObserver::~RegisteredObserver() = default;
162
163void RegisteredObserver::visit_edges(Cell::Visitor& visitor)
164{
165 Base::visit_edges(visitor);
166 visitor.visit(m_observer.ptr());
167}
168
169JS::NonnullGCPtr<TransientRegisteredObserver> TransientRegisteredObserver::create(MutationObserver& observer, MutationObserverInit const& options, RegisteredObserver& source)
170{
171 return observer.heap().allocate_without_realm<TransientRegisteredObserver>(observer, options, source);
172}
173
174TransientRegisteredObserver::TransientRegisteredObserver(MutationObserver& observer, MutationObserverInit const& options, RegisteredObserver& source)
175 : RegisteredObserver(observer, options)
176 , m_source(source)
177{
178}
179
180TransientRegisteredObserver::~TransientRegisteredObserver() = default;
181
182void TransientRegisteredObserver::visit_edges(Cell::Visitor& visitor)
183{
184 Base::visit_edges(visitor);
185 visitor.visit(m_source.ptr());
186}
187
188}