/* * Copyright (c) 2022, Luke Wilde * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include namespace Web::DOM { WebIDL::ExceptionOr> MutationObserver::construct_impl(JS::Realm& realm, JS::GCPtr callback) { return MUST_OR_THROW_OOM(realm.heap().allocate(realm, realm, callback)); } // https://dom.spec.whatwg.org/#dom-mutationobserver-mutationobserver MutationObserver::MutationObserver(JS::Realm& realm, JS::GCPtr 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(realm.vm().custom_data()); agent_custom_data->mutation_observers.append(*this); } MutationObserver::~MutationObserver() = default; JS::ThrowCompletionOr MutationObserver::initialize(JS::Realm& realm) { MUST_OR_THROW_OOM(Base::initialize(realm)); set_prototype(&Bindings::ensure_web_prototype(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 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([®istered_observer](RegisteredObserver& observer) { return is(observer) && verify_cast(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()); } 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> MutationObserver::take_records() { // 1. Let records be a clone of this’s record queue. Vector> 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::create(MutationObserver& observer, MutationObserverInit const& options) { return observer.heap().allocate_without_realm(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::create(MutationObserver& observer, MutationObserverInit const& options, RegisteredObserver& source) { return observer.heap().allocate_without_realm(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()); } }