Serenity Operating System
at master 188 lines 8.1 kB view raw
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([&registered_observer](RegisteredObserver& observer) { 95 return is<TransientRegisteredObserver>(observer) && verify_cast<TransientRegisteredObserver>(observer).source().ptr() == &registered_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}