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 +#include #include #include #include #include +#include #include +#include +#include #include #include #include @@ -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(*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(registered_observer) && static_cast(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 #include #include #include +#include #include 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 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 #include #include +#include +#include +#include +#include #include #include #include @@ -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 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +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(window_object.vm().custom_data()); + agent_custom_data->mutation_observers.append(*this); +} + +// https://dom.spec.whatwg.org/#dom-mutationobserver-observe +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 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(observer) && verify_cast(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 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 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace Web::DOM { + +// https://dom.spec.whatwg.org/#dictdef-mutationobserverinit +struct MutationObserverInit { + bool child_list { false }; + Optional attributes; + Optional character_data; + bool subtree { false }; + Optional attribute_old_value; + Optional character_data_old_value; + Optional> attribute_filter; +}; + +// https://dom.spec.whatwg.org/#mutationobserver +class MutationObserver final + : public RefCounted + , public Bindings::Wrappable { +public: + using WrapperType = Bindings::MutationObserverWrapper; + + static NonnullRefPtr 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 observe(Node& target, MutationObserverInit options = {}); + void disconnect(); + NonnullRefPtrVector take_records(); + + Vector>& node_list() { return m_node_list; } + Vector> const& node_list() const { return m_node_list; } + + Bindings::CallbackType& callback() { return m_callback; } + + void enqueue_record(Badge, NonnullRefPtr 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> m_node_list; + + // https://dom.spec.whatwg.org/#concept-mo-queue + NonnullRefPtrVector m_record_queue; +}; + +// https://dom.spec.whatwg.org/#registered-observer +struct RegisteredObserver : public RefCounted { + static NonnullRefPtr 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 observer; + MutationObserverInit options; +}; + +// https://dom.spec.whatwg.org/#transient-registered-observer +struct TransientRegisteredObserver final : public RegisteredObserver { + static NonnullRefPtr 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 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 +#import + +[Exposed=Window] +interface MutationObserver { + + constructor(MutationCallback callback); + + undefined observe(Node target, optional MutationObserverInit options = {}); + undefined disconnect(); + sequence takeRecords(); + +}; + +callback MutationCallback = undefined (sequence mutations, MutationObserver observer); + +dictionary MutationObserverInit { + + boolean childList = false; + boolean attributes; + boolean characterData; + boolean subtree = false; + boolean attributeOldValue; + boolean characterDataOldValue; + sequence 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 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +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 m_target; + NonnullRefPtr m_added_nodes; + NonnullRefPtr m_removed_nodes; + RefPtr m_previous_sibling; + RefPtr m_next_sibling; + String m_attribute_name; + String m_attribute_namespace; + String m_old_value; +}; + +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) +{ + 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 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +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 + , public Bindings::Wrappable { +public: + using WrapperType = Bindings::MutationRecordWrapper; + + static NonnullRefPtr 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 +#import + +[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 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +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 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +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 #include #include +#include #include namespace Web::DOM { @@ -223,6 +224,11 @@ public: size_t length() const; + NonnullRefPtrVector& registered_observers_list() { return m_registered_observer_list; } + NonnullRefPtrVector 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 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)