diff options
author | Luke Wilde <lukew@serenityos.org> | 2022-07-11 16:37:51 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-07-11 22:35:08 +0200 |
commit | c9ba5531e0e0985408dbc0905263ce3a38e1ad20 (patch) | |
tree | 5ac20a3c3f96392b402a6f95024ad12ddb0fbf8b /Userland/Libraries | |
parent | 116a7b74feef6e261b0c8c1dedffacb5a0d7e047 (diff) | |
download | serenity-c9ba5531e0e0985408dbc0905263ce3a38e1ad20.zip |
LibWeb: Introduce Mutation{Record,Observer} and observer microtasks
Diffstat (limited to 'Userland/Libraries')
-rw-r--r-- | Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp | 74 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Bindings/MainThreadVM.h | 12 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Bindings/WindowObjectHelper.h | 6 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CMakeLists.txt | 3 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/DOM/MutationObserver.cpp | 123 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/DOM/MutationObserver.h | 108 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/DOM/MutationObserver.idl | 27 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/DOM/MutationRecord.cpp | 57 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/DOM/MutationRecord.h | 38 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/DOM/MutationRecord.idl | 17 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/DOM/MutationType.cpp | 28 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/DOM/MutationType.h | 22 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/DOM/Node.h | 10 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Forward.h | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/idl_files.cmake | 2 |
15 files changed, 531 insertions, 0 deletions
diff --git a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp index 9329b77ea6..e8432f0d5e 100644 --- a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp +++ b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp @@ -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. + }); +} + } diff --git a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.h b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.h index e4b9333eb5..a085543d6e 100644 --- a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.h +++ b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.h @@ -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&); } diff --git a/Userland/Libraries/LibWeb/Bindings/WindowObjectHelper.h b/Userland/Libraries/LibWeb/Bindings/WindowObjectHelper.h index da6dda12f0..09f3b5c2f8 100644 --- a/Userland/Libraries/LibWeb/Bindings/WindowObjectHelper.h +++ b/Userland/Libraries/LibWeb/Bindings/WindowObjectHelper.h @@ -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) \ diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index e9e6cacfcc..5c083401c0 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -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 diff --git a/Userland/Libraries/LibWeb/DOM/MutationObserver.cpp b/Userland/Libraries/LibWeb/DOM/MutationObserver.cpp new file mode 100644 index 0000000000..f489a7d016 --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/MutationObserver.cpp @@ -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; +} + +} diff --git a/Userland/Libraries/LibWeb/DOM/MutationObserver.h b/Userland/Libraries/LibWeb/DOM/MutationObserver.h new file mode 100644 index 0000000000..e7dcf942ee --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/MutationObserver.h @@ -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; +}; + +} diff --git a/Userland/Libraries/LibWeb/DOM/MutationObserver.idl b/Userland/Libraries/LibWeb/DOM/MutationObserver.idl new file mode 100644 index 0000000000..3e24042199 --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/MutationObserver.idl @@ -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; + +}; diff --git a/Userland/Libraries/LibWeb/DOM/MutationRecord.cpp b/Userland/Libraries/LibWeb/DOM/MutationRecord.cpp new file mode 100644 index 0000000000..ded0a0ec71 --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/MutationRecord.cpp @@ -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)); +} + +} diff --git a/Userland/Libraries/LibWeb/DOM/MutationRecord.h b/Userland/Libraries/LibWeb/DOM/MutationRecord.h new file mode 100644 index 0000000000..0c1b279400 --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/MutationRecord.h @@ -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; +}; + +} diff --git a/Userland/Libraries/LibWeb/DOM/MutationRecord.idl b/Userland/Libraries/LibWeb/DOM/MutationRecord.idl new file mode 100644 index 0000000000..0914177892 --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/MutationRecord.idl @@ -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; + +}; diff --git a/Userland/Libraries/LibWeb/DOM/MutationType.cpp b/Userland/Libraries/LibWeb/DOM/MutationType.cpp new file mode 100644 index 0000000000..32787e8426 --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/MutationType.cpp @@ -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; +} + +} diff --git a/Userland/Libraries/LibWeb/DOM/MutationType.h b/Userland/Libraries/LibWeb/DOM/MutationType.h new file mode 100644 index 0000000000..23f0b8c62a --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/MutationType.h @@ -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 + +} diff --git a/Userland/Libraries/LibWeb/DOM/Node.h b/Userland/Libraries/LibWeb/DOM/Node.h index 89710a38a4..293e666b29 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.h +++ b/Userland/Libraries/LibWeb/DOM/Node.h @@ -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; }; } diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 534936f6ed..f01070f085 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -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; diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index adba5773bc..9d841cb89f 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -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) |