mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 16:17:45 +00:00
LibWeb: Introduce Mutation{Record,Observer} and observer microtasks
This commit is contained in:
parent
116a7b74fe
commit
c9ba5531e0
15 changed files with 531 additions and 0 deletions
|
@ -6,11 +6,15 @@
|
|||
*/
|
||||
|
||||
#include <LibJS/Module.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
#include <LibJS/Runtime/Environment.h>
|
||||
#include <LibJS/Runtime/FinalizationRegistry.h>
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibWeb/Bindings/IDLAbstractOperations.h>
|
||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||
#include <LibWeb/Bindings/MutationObserverWrapper.h>
|
||||
#include <LibWeb/Bindings/MutationRecordWrapper.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/HTML/PromiseRejectionEvent.h>
|
||||
#include <LibWeb/HTML/Scripting/ClassicScript.h>
|
||||
|
@ -286,4 +290,74 @@ JS::VM& main_thread_vm()
|
|||
return *vm;
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#queue-a-mutation-observer-compound-microtask
|
||||
void queue_mutation_observer_microtask(DOM::Document& document)
|
||||
{
|
||||
// FIXME: Is this the correct VM?
|
||||
auto& vm = main_thread_vm();
|
||||
auto& custom_data = verify_cast<WebEngineCustomData>(*vm.custom_data());
|
||||
|
||||
// 1. If the surrounding agent’s mutation observer microtask queued is true, then return.
|
||||
if (custom_data.mutation_observer_microtask_queued)
|
||||
return;
|
||||
|
||||
// 2. Set the surrounding agent’s mutation observer microtask queued to true.
|
||||
custom_data.mutation_observer_microtask_queued = true;
|
||||
|
||||
// 3. Queue a microtask to notify mutation observers.
|
||||
// NOTE: This uses the implied document concept. In the case of mutation observers, it is always done in a node context, so document should be that node's document.
|
||||
// FIXME: Is it safe to pass custom_data through?
|
||||
HTML::queue_a_microtask(&document, [&custom_data]() {
|
||||
// 1. Set the surrounding agent’s mutation observer microtask queued to false.
|
||||
custom_data.mutation_observer_microtask_queued = false;
|
||||
|
||||
// 2. Let notifySet be a clone of the surrounding agent’s mutation observers.
|
||||
auto notify_set = custom_data.mutation_observers;
|
||||
|
||||
// FIXME: 3. Let signalSet be a clone of the surrounding agent’s signal slots.
|
||||
|
||||
// FIXME: 4. Empty the surrounding agent’s signal slots.
|
||||
|
||||
// 5. For each mo of notifySet:
|
||||
for (auto& mutation_observer : notify_set) {
|
||||
// 1. Let records be a clone of mo’s record queue.
|
||||
// 2. Empty mo’s record queue.
|
||||
auto records = mutation_observer.take_records();
|
||||
|
||||
// 3. For each node of mo’s node list, remove all transient registered observers whose observer is mo from node’s registered observer list.
|
||||
for (auto& node : mutation_observer.node_list()) {
|
||||
// FIXME: Is this correct?
|
||||
if (node.is_null())
|
||||
continue;
|
||||
|
||||
node->registered_observers_list().remove_all_matching([&mutation_observer](DOM::RegisteredObserver& registered_observer) {
|
||||
return is<DOM::TransientRegisteredObserver>(registered_observer) && static_cast<DOM::TransientRegisteredObserver&>(registered_observer).observer.ptr() == &mutation_observer;
|
||||
});
|
||||
}
|
||||
|
||||
// 4. If records is not empty, then invoke mo’s callback with « records, mo », and mo. If this throws an exception, catch it, and report the exception.
|
||||
if (!records.is_empty()) {
|
||||
auto& callback = mutation_observer.callback();
|
||||
auto& global_object = callback.callback_context.global_object();
|
||||
|
||||
auto* wrapped_records = MUST(JS::Array::create(global_object, 0));
|
||||
for (size_t i = 0; i < records.size(); ++i) {
|
||||
auto& record = records.at(i);
|
||||
auto* wrapped_record = Bindings::wrap(global_object, record);
|
||||
auto property_index = JS::PropertyKey { i };
|
||||
MUST(wrapped_records->create_data_property(property_index, wrapped_record));
|
||||
}
|
||||
|
||||
auto* wrapped_mutation_observer = Bindings::wrap(global_object, mutation_observer);
|
||||
|
||||
auto result = IDL::invoke_callback(callback, wrapped_mutation_observer, wrapped_records, wrapped_mutation_observer);
|
||||
if (result.is_abrupt())
|
||||
HTML::report_exception(result);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: 6. For each slot of signalSet, fire an event named slotchange, with its bubbles attribute set to true, at slot.
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,9 +7,11 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/NonnullRefPtrVector.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Runtime/JobCallback.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibWeb/DOM/MutationObserver.h>
|
||||
#include <LibWeb/HTML/EventLoop/EventLoop.h>
|
||||
|
||||
namespace Web::Bindings {
|
||||
|
@ -18,6 +20,15 @@ struct WebEngineCustomData final : public JS::VM::CustomData {
|
|||
virtual ~WebEngineCustomData() override = default;
|
||||
|
||||
HTML::EventLoop event_loop;
|
||||
|
||||
// FIXME: These should only be on similar-origin window agents, but we don't currently differentiate agent types.
|
||||
|
||||
// https://dom.spec.whatwg.org/#mutation-observer-compound-microtask-queued-flag
|
||||
bool mutation_observer_microtask_queued { false };
|
||||
|
||||
// https://dom.spec.whatwg.org/#mutation-observer-list
|
||||
// FIXME: This should be a set.
|
||||
NonnullRefPtrVector<DOM::MutationObserver> mutation_observers;
|
||||
};
|
||||
|
||||
struct WebEngineCustomJobCallbackData final : public JS::JobCallback::CustomData {
|
||||
|
@ -35,5 +46,6 @@ struct WebEngineCustomJobCallbackData final : public JS::JobCallback::CustomData
|
|||
|
||||
HTML::ClassicScript* active_script();
|
||||
JS::VM& main_thread_vm();
|
||||
void queue_mutation_observer_microtask(DOM::Document&);
|
||||
|
||||
}
|
||||
|
|
|
@ -254,6 +254,10 @@
|
|||
#include <LibWeb/Bindings/MessageEventPrototype.h>
|
||||
#include <LibWeb/Bindings/MouseEventConstructor.h>
|
||||
#include <LibWeb/Bindings/MouseEventPrototype.h>
|
||||
#include <LibWeb/Bindings/MutationObserverConstructor.h>
|
||||
#include <LibWeb/Bindings/MutationObserverPrototype.h>
|
||||
#include <LibWeb/Bindings/MutationRecordConstructor.h>
|
||||
#include <LibWeb/Bindings/MutationRecordPrototype.h>
|
||||
#include <LibWeb/Bindings/NavigatorConstructor.h>
|
||||
#include <LibWeb/Bindings/NavigatorPrototype.h>
|
||||
#include <LibWeb/Bindings/NodeConstructor.h>
|
||||
|
@ -492,6 +496,8 @@
|
|||
ADD_WINDOW_OBJECT_INTERFACE(MessageChannel) \
|
||||
ADD_WINDOW_OBJECT_INTERFACE(MessageEvent) \
|
||||
ADD_WINDOW_OBJECT_INTERFACE(MouseEvent) \
|
||||
ADD_WINDOW_OBJECT_INTERFACE(MutationObserver) \
|
||||
ADD_WINDOW_OBJECT_INTERFACE(MutationRecord) \
|
||||
ADD_WINDOW_OBJECT_INTERFACE(Navigator) \
|
||||
ADD_WINDOW_OBJECT_INTERFACE(Node) \
|
||||
ADD_WINDOW_OBJECT_INTERFACE(NodeIterator) \
|
||||
|
|
|
@ -94,6 +94,9 @@ set(SOURCES
|
|||
DOM/EventTarget.cpp
|
||||
DOM/HTMLCollection.cpp
|
||||
DOM/LiveNodeList.cpp
|
||||
DOM/MutationObserver.cpp
|
||||
DOM/MutationRecord.cpp
|
||||
DOM/MutationType.cpp
|
||||
DOM/NamedNodeMap.cpp
|
||||
DOM/Node.cpp
|
||||
DOM/NodeIterator.cpp
|
||||
|
|
123
Userland/Libraries/LibWeb/DOM/MutationObserver.cpp
Normal file
123
Userland/Libraries/LibWeb/DOM/MutationObserver.cpp
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||
#include <LibWeb/Bindings/WindowObject.h>
|
||||
#include <LibWeb/DOM/MutationObserver.h>
|
||||
#include <LibWeb/DOM/Node.h>
|
||||
|
||||
namespace Web::DOM {
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-mutationobserver-mutationobserver
|
||||
MutationObserver::MutationObserver(Bindings::WindowObject& window_object, Bindings::CallbackType callback)
|
||||
: 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>(window_object.vm().custom_data());
|
||||
agent_custom_data->mutation_observers.append(*this);
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-mutationobserver-observe
|
||||
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 SimpleException { SimpleExceptionType::TypeError, "Options must have one of childList, attributes or characterData set to true." };
|
||||
|
||||
// 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 SimpleException { SimpleExceptionType::TypeError, "attributes must be true if attributeOldValue is true." };
|
||||
|
||||
// 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 SimpleException { SimpleExceptionType::TypeError, "attributes must be true if attributeFilter is present." };
|
||||
|
||||
// 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 SimpleException { SimpleExceptionType::TypeError, "characterData must be true if characterDataOldValue is true." };
|
||||
|
||||
// 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<TransientRegisteredObserver>(observer) && verify_cast<TransientRegisteredObserver>(observer).source.ptr() == ®istered_observer;
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Set registered’s options to options.
|
||||
registered_observer.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
|
||||
NonnullRefPtrVector<MutationRecord> MutationObserver::take_records()
|
||||
{
|
||||
// 1. Let records be a clone of this’s record queue.
|
||||
auto records = m_record_queue;
|
||||
|
||||
// 2. Empty this’s record queue.
|
||||
m_record_queue.clear();
|
||||
|
||||
// 3. Return records.
|
||||
return records;
|
||||
}
|
||||
|
||||
}
|
108
Userland/Libraries/LibWeb/DOM/MutationObserver.h
Normal file
108
Userland/Libraries/LibWeb/DOM/MutationObserver.h
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/NonnullRefPtrVector.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <LibJS/Heap/Handle.h>
|
||||
#include <LibWeb/Bindings/CallbackType.h>
|
||||
#include <LibWeb/Bindings/Wrappable.h>
|
||||
#include <LibWeb/DOM/ExceptionOr.h>
|
||||
#include <LibWeb/DOM/MutationRecord.h>
|
||||
|
||||
namespace Web::DOM {
|
||||
|
||||
// https://dom.spec.whatwg.org/#dictdef-mutationobserverinit
|
||||
struct MutationObserverInit {
|
||||
bool child_list { false };
|
||||
Optional<bool> attributes;
|
||||
Optional<bool> character_data;
|
||||
bool subtree { false };
|
||||
Optional<bool> attribute_old_value;
|
||||
Optional<bool> character_data_old_value;
|
||||
Optional<Vector<String>> attribute_filter;
|
||||
};
|
||||
|
||||
// https://dom.spec.whatwg.org/#mutationobserver
|
||||
class MutationObserver final
|
||||
: public RefCounted<MutationObserver>
|
||||
, public Bindings::Wrappable {
|
||||
public:
|
||||
using WrapperType = Bindings::MutationObserverWrapper;
|
||||
|
||||
static NonnullRefPtr<MutationObserver> create_with_global_object(Bindings::WindowObject& window_object, Bindings::CallbackType callback)
|
||||
{
|
||||
return adopt_ref(*new MutationObserver(window_object, move(callback)));
|
||||
}
|
||||
|
||||
virtual ~MutationObserver() override = default;
|
||||
|
||||
ExceptionOr<void> observe(Node& target, MutationObserverInit options = {});
|
||||
void disconnect();
|
||||
NonnullRefPtrVector<MutationRecord> take_records();
|
||||
|
||||
Vector<WeakPtr<Node>>& node_list() { return m_node_list; }
|
||||
Vector<WeakPtr<Node>> const& node_list() const { return m_node_list; }
|
||||
|
||||
Bindings::CallbackType& callback() { return m_callback; }
|
||||
|
||||
void enqueue_record(Badge<Node>, NonnullRefPtr<MutationRecord> mutation_record)
|
||||
{
|
||||
m_record_queue.append(move(mutation_record));
|
||||
}
|
||||
|
||||
private:
|
||||
MutationObserver(Bindings::WindowObject& window_object, Bindings::CallbackType callback);
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-mo-callback
|
||||
Bindings::CallbackType m_callback;
|
||||
|
||||
// https://dom.spec.whatwg.org/#mutationobserver-node-list
|
||||
Vector<WeakPtr<Node>> m_node_list;
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-mo-queue
|
||||
NonnullRefPtrVector<MutationRecord> m_record_queue;
|
||||
};
|
||||
|
||||
// https://dom.spec.whatwg.org/#registered-observer
|
||||
struct RegisteredObserver : public RefCounted<RegisteredObserver> {
|
||||
static NonnullRefPtr<RegisteredObserver> create(MutationObserver& observer, MutationObserverInit& options)
|
||||
{
|
||||
return adopt_ref(*new RegisteredObserver(observer, options));
|
||||
}
|
||||
|
||||
RegisteredObserver(MutationObserver& observer, MutationObserverInit& options)
|
||||
: observer(observer)
|
||||
, options(options)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~RegisteredObserver() = default;
|
||||
|
||||
NonnullRefPtr<MutationObserver> observer;
|
||||
MutationObserverInit options;
|
||||
};
|
||||
|
||||
// https://dom.spec.whatwg.org/#transient-registered-observer
|
||||
struct TransientRegisteredObserver final : public RegisteredObserver {
|
||||
static NonnullRefPtr<TransientRegisteredObserver> create(MutationObserver& observer, MutationObserverInit& options, RegisteredObserver& source)
|
||||
{
|
||||
return adopt_ref(*new TransientRegisteredObserver(observer, options, source));
|
||||
}
|
||||
|
||||
TransientRegisteredObserver(MutationObserver& observer, MutationObserverInit& options, RegisteredObserver& source)
|
||||
: RegisteredObserver(observer, options)
|
||||
, source(source)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~TransientRegisteredObserver() override = default;
|
||||
|
||||
NonnullRefPtr<RegisteredObserver> source;
|
||||
};
|
||||
|
||||
}
|
27
Userland/Libraries/LibWeb/DOM/MutationObserver.idl
Normal file
27
Userland/Libraries/LibWeb/DOM/MutationObserver.idl
Normal file
|
@ -0,0 +1,27 @@
|
|||
#import <DOM/MutationRecord.idl>
|
||||
#import <DOM/Node.idl>
|
||||
|
||||
[Exposed=Window]
|
||||
interface MutationObserver {
|
||||
|
||||
constructor(MutationCallback callback);
|
||||
|
||||
undefined observe(Node target, optional MutationObserverInit options = {});
|
||||
undefined disconnect();
|
||||
sequence<MutationRecord> takeRecords();
|
||||
|
||||
};
|
||||
|
||||
callback MutationCallback = undefined (sequence<MutationRecord> mutations, MutationObserver observer);
|
||||
|
||||
dictionary MutationObserverInit {
|
||||
|
||||
boolean childList = false;
|
||||
boolean attributes;
|
||||
boolean characterData;
|
||||
boolean subtree = false;
|
||||
boolean attributeOldValue;
|
||||
boolean characterDataOldValue;
|
||||
sequence<DOMString> attributeFilter;
|
||||
|
||||
};
|
57
Userland/Libraries/LibWeb/DOM/MutationRecord.cpp
Normal file
57
Userland/Libraries/LibWeb/DOM/MutationRecord.cpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/DOM/MutationRecord.h>
|
||||
#include <LibWeb/DOM/Node.h>
|
||||
#include <LibWeb/DOM/NodeList.h>
|
||||
|
||||
namespace Web::DOM {
|
||||
|
||||
class MutationRecordImpl final : public MutationRecord {
|
||||
public:
|
||||
MutationRecordImpl(FlyString const& type, Node& target, NodeList& added_nodes, NodeList& removed_nodes, Node* previous_sibling, Node* next_sibling, String const& attribute_name, String const& attribute_namespace, String const& old_value)
|
||||
: m_type(type)
|
||||
, m_target(target)
|
||||
, m_added_nodes(added_nodes)
|
||||
, m_removed_nodes(removed_nodes)
|
||||
, m_previous_sibling(previous_sibling)
|
||||
, m_next_sibling(next_sibling)
|
||||
, m_attribute_name(attribute_name)
|
||||
, m_attribute_namespace(attribute_namespace)
|
||||
, m_old_value(old_value)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~MutationRecordImpl() override = default;
|
||||
|
||||
virtual FlyString const& type() const override { return m_type; }
|
||||
virtual Node const* target() const override { return m_target; }
|
||||
virtual NodeList const* added_nodes() const override { return m_added_nodes; }
|
||||
virtual NodeList const* removed_nodes() const override { return m_removed_nodes; }
|
||||
virtual Node const* previous_sibling() const override { return m_previous_sibling; }
|
||||
virtual Node const* next_sibling() const override { return m_next_sibling; }
|
||||
virtual String const& attribute_name() const override { return m_attribute_name; }
|
||||
virtual String const& attribute_namespace() const override { return m_attribute_namespace; }
|
||||
virtual String const& old_value() const override { return m_old_value; }
|
||||
|
||||
private:
|
||||
FlyString m_type;
|
||||
NonnullRefPtr<Node> m_target;
|
||||
NonnullRefPtr<NodeList> m_added_nodes;
|
||||
NonnullRefPtr<NodeList> m_removed_nodes;
|
||||
RefPtr<Node> m_previous_sibling;
|
||||
RefPtr<Node> m_next_sibling;
|
||||
String m_attribute_name;
|
||||
String m_attribute_namespace;
|
||||
String m_old_value;
|
||||
};
|
||||
|
||||
NonnullRefPtr<MutationRecord> MutationRecord::create(FlyString const& type, Node& target, NodeList& added_nodes, NodeList& removed_nodes, Node* previous_sibling, Node* next_sibling, String const& attribute_name, String const& attribute_namespace, String const& old_value)
|
||||
{
|
||||
return adopt_ref(*new MutationRecordImpl(type, target, added_nodes, removed_nodes, previous_sibling, next_sibling, attribute_name, attribute_namespace, old_value));
|
||||
}
|
||||
|
||||
}
|
38
Userland/Libraries/LibWeb/DOM/MutationRecord.h
Normal file
38
Userland/Libraries/LibWeb/DOM/MutationRecord.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/RefCounted.h>
|
||||
#include <LibWeb/Bindings/Wrappable.h>
|
||||
|
||||
namespace Web::DOM {
|
||||
|
||||
// https://dom.spec.whatwg.org/#mutationrecord
|
||||
// NOTE: This is implemented as a pure virtual interface with the actual implementation in the CPP file to prevent this circular dependency: Node.h -> MutationRecord.h -> MutationObserver.h -> Node.h
|
||||
// This is also why this uses raw pointers and references, since using (NN)RP requires us to include the templated type, even in a header specifying a function return type.
|
||||
class MutationRecord
|
||||
: public RefCounted<MutationRecord>
|
||||
, public Bindings::Wrappable {
|
||||
public:
|
||||
using WrapperType = Bindings::MutationRecordWrapper;
|
||||
|
||||
static NonnullRefPtr<MutationRecord> create(FlyString const& type, Node& target, NodeList& added_nodes, NodeList& removed_nodes, Node* previous_sibling, Node* next_sibling, String const& attribute_name, String const& attribute_namespace, String const& old_value);
|
||||
|
||||
virtual ~MutationRecord() override = default;
|
||||
|
||||
virtual FlyString const& type() const = 0;
|
||||
virtual Node const* target() const = 0;
|
||||
virtual NodeList const* added_nodes() const = 0;
|
||||
virtual NodeList const* removed_nodes() const = 0;
|
||||
virtual Node const* previous_sibling() const = 0;
|
||||
virtual Node const* next_sibling() const = 0;
|
||||
virtual String const& attribute_name() const = 0;
|
||||
virtual String const& attribute_namespace() const = 0;
|
||||
virtual String const& old_value() const = 0;
|
||||
};
|
||||
|
||||
}
|
17
Userland/Libraries/LibWeb/DOM/MutationRecord.idl
Normal file
17
Userland/Libraries/LibWeb/DOM/MutationRecord.idl
Normal file
|
@ -0,0 +1,17 @@
|
|||
#import <DOM/Node.idl>
|
||||
#import <DOM/NodeList.idl>
|
||||
|
||||
[Exposed=Window]
|
||||
interface MutationRecord {
|
||||
|
||||
readonly attribute DOMString type;
|
||||
[SameObject] readonly attribute Node target;
|
||||
[SameObject] readonly attribute NodeList addedNodes;
|
||||
[SameObject] readonly attribute NodeList removedNodes;
|
||||
readonly attribute Node? previousSibling;
|
||||
readonly attribute Node? nextSibling;
|
||||
readonly attribute DOMString? attributeName;
|
||||
readonly attribute DOMString? attributeNamespace;
|
||||
readonly attribute DOMString? oldValue;
|
||||
|
||||
};
|
28
Userland/Libraries/LibWeb/DOM/MutationType.cpp
Normal file
28
Userland/Libraries/LibWeb/DOM/MutationType.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/DOM/MutationType.h>
|
||||
|
||||
namespace Web::DOM::MutationType {
|
||||
|
||||
#define __ENUMERATE_MUTATION_TYPE(name) FlyString name;
|
||||
ENUMERATE_MUTATION_TYPES
|
||||
#undef __ENUMERATE_MUTATION_TYPE
|
||||
|
||||
[[gnu::constructor]] static void initialize()
|
||||
{
|
||||
static bool s_initialized = false;
|
||||
if (s_initialized)
|
||||
return;
|
||||
|
||||
#define __ENUMERATE_MUTATION_TYPE(name) name = #name;
|
||||
ENUMERATE_MUTATION_TYPES
|
||||
#undef __ENUMERATE_MUTATION_TYPE
|
||||
|
||||
s_initialized = true;
|
||||
}
|
||||
|
||||
}
|
22
Userland/Libraries/LibWeb/DOM/MutationType.h
Normal file
22
Userland/Libraries/LibWeb/DOM/MutationType.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/FlyString.h>
|
||||
|
||||
namespace Web::DOM::MutationType {
|
||||
|
||||
#define ENUMERATE_MUTATION_TYPES \
|
||||
__ENUMERATE_MUTATION_TYPE(attributes) \
|
||||
__ENUMERATE_MUTATION_TYPE(characterData) \
|
||||
__ENUMERATE_MUTATION_TYPE(childList)
|
||||
|
||||
#define __ENUMERATE_MUTATION_TYPE(name) extern FlyString name;
|
||||
ENUMERATE_MUTATION_TYPES
|
||||
#undef __ENUMERATE_MUTATION_TYPE
|
||||
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
#include <LibWeb/Bindings/Wrappable.h>
|
||||
#include <LibWeb/DOM/EventTarget.h>
|
||||
#include <LibWeb/DOM/ExceptionOr.h>
|
||||
#include <LibWeb/DOM/MutationObserver.h>
|
||||
#include <LibWeb/TreeNode.h>
|
||||
|
||||
namespace Web::DOM {
|
||||
|
@ -223,6 +224,11 @@ public:
|
|||
|
||||
size_t length() const;
|
||||
|
||||
NonnullRefPtrVector<RegisteredObserver>& registered_observers_list() { return m_registered_observer_list; }
|
||||
NonnullRefPtrVector<RegisteredObserver> const& registered_observers_list() const { return m_registered_observer_list; }
|
||||
|
||||
void add_registered_observer(RegisteredObserver& registered_observer) { m_registered_observer_list.append(registered_observer); }
|
||||
|
||||
protected:
|
||||
Node(Document&, NodeType);
|
||||
|
||||
|
@ -233,6 +239,10 @@ protected:
|
|||
bool m_child_needs_style_update { false };
|
||||
|
||||
i32 m_id;
|
||||
|
||||
// https://dom.spec.whatwg.org/#registered-observer-list
|
||||
// "Nodes have a strong reference to registered observers in their registered observer list." https://dom.spec.whatwg.org/#garbage-collection
|
||||
NonnullRefPtrVector<RegisteredObserver> m_registered_observer_list;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -137,6 +137,8 @@ class EventTarget;
|
|||
class HTMLCollection;
|
||||
class IDLEventListener;
|
||||
class LiveNodeList;
|
||||
class MutationObserver;
|
||||
class MutationRecord;
|
||||
class NamedNodeMap;
|
||||
class Node;
|
||||
class NodeFilter;
|
||||
|
@ -535,6 +537,8 @@ class MessageChannelWrapper;
|
|||
class MessageEventWrapper;
|
||||
class MessagePortWrapper;
|
||||
class MouseEventWrapper;
|
||||
class MutationObserverWrapper;
|
||||
class MutationRecordWrapper;
|
||||
class NamedNodeMapWrapper;
|
||||
class NodeFilterWrapper;
|
||||
class NodeIteratorWrapper;
|
||||
|
|
|
@ -38,6 +38,8 @@ libweb_js_wrapper(DOM/Element)
|
|||
libweb_js_wrapper(DOM/Event)
|
||||
libweb_js_wrapper(DOM/EventTarget)
|
||||
libweb_js_wrapper(DOM/HTMLCollection)
|
||||
libweb_js_wrapper(DOM/MutationRecord)
|
||||
libweb_js_wrapper(DOM/MutationObserver)
|
||||
libweb_js_wrapper(DOM/NamedNodeMap)
|
||||
libweb_js_wrapper(DOM/Node)
|
||||
libweb_js_wrapper(DOM/NodeIterator)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue