ladybird/Userland/Libraries/LibWeb/DOM/MutationObserver.cpp
Shannon Booth bad44f8fc9 LibWeb: Remove Bindings/Forward.h from LibWeb/Forward.h
This was resulting in a whole lot of rebuilding whenever a new IDL
interface was added.

Instead, just directly include the prototype in every C++ file which
needs it. While we only really need a forward declaration in each cpp
file; including the full prototype header (which itself only includes
LibJS/Object.h, which is already transitively brought in by
PlatformObject) - it seems like a small price to pay compared to what
feels like a full rebuild of LibWeb whenever a new IDL file is added.

Given all of these includes are only needed for the ::initialize
method, there is probably a smart way of avoiding this problem
altogether. I've considered both using some macro trickery or generating
these functions somehow instead.
2024-04-27 18:29:35 -04:00

202 lines
8.3 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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/Bindings/MutationObserverPrototype.h>
#include <LibWeb/DOM/MutationObserver.h>
#include <LibWeb/DOM/Node.h>
namespace Web::DOM {
JS_DEFINE_ALLOCATOR(MutationObserver);
JS_DEFINE_ALLOCATOR(TransientRegisteredObserver);
WebIDL::ExceptionOr<JS::NonnullGCPtr<MutationObserver>> MutationObserver::construct_impl(JS::Realm& realm, JS::GCPtr<WebIDL::CallbackType> callback)
{
return 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 thiss callback to callback.
// 2. Append this to thiss relevant agents mutation observers.
auto* agent_custom_data = verify_cast<Bindings::WebEngineCustomData>(realm.vm().custom_data());
agent_custom_data->mutation_observers.append(*this);
}
MutationObserver::~MutationObserver()
{
auto* agent_custom_data = verify_cast<Bindings::WebEngineCustomData>(vm().custom_data());
agent_custom_data->mutation_observers.remove_all_matching([this](auto& observer) {
return observer.ptr() == this;
});
}
void MutationObserver::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(MutationObserver);
}
void MutationObserver::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_callback);
visitor.visit(m_record_queue);
}
// 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 targets registered observer list, if registereds observer is this:
bool updated_existing_observer = false;
if (target.registered_observer_list()) {
for (auto& registered_observer : *target.registered_observer_list()) {
if (registered_observer->observer().ptr() != this)
continue;
updated_existing_observer = true;
// 1. For each node of thiss node list, remove all transient registered observers whose source is registered from nodes registered observer list.
for (auto& node : m_node_list) {
// FIXME: Is this correct?
if (node.is_null())
continue;
if (node->registered_observer_list()) {
node->registered_observer_list()->remove_all_matching([&registered_observer](RegisteredObserver& observer) {
return is<TransientRegisteredObserver>(observer) && verify_cast<TransientRegisteredObserver>(observer).source().ptr() == registered_observer;
});
}
}
// 2. Set registereds 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 targets registered observer list.
auto new_registered_observer = RegisteredObserver::create(*this, options);
target.add_registered_observer(new_registered_observer);
// 2. Append target to thiss 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 thiss node list, remove any registered observer from nodes registered observer list for which this is the observer.
for (auto& node : m_node_list) {
// FIXME: Is this correct?
if (node.is_null())
continue;
if (node->registered_observer_list()) {
node->registered_observer_list()->remove_all_matching([this](RegisteredObserver& registered_observer) {
return registered_observer.observer().ptr() == this;
});
}
}
// 2. Empty thiss 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 thiss record queue.
Vector<JS::Handle<MutationRecord>> records;
for (auto& record : m_record_queue)
records.append(*record);
// 2. Empty thiss 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);
}
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);
}
}