diff --git a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp index 9964f7c4ce..f7a59dc91e 100644 --- a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp +++ b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -356,22 +355,22 @@ void queue_mutation_observer_microtask(DOM::Document& document) 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(); + 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()) { + 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; + return is(registered_observer) && static_cast(registered_observer).observer().ptr() == mutation_observer.ptr(); }); } // 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& callback = mutation_observer->callback(); auto& realm = callback.callback_context.realm(); auto* wrapped_records = MUST(JS::Array::create(realm, 0)); @@ -381,9 +380,7 @@ void queue_mutation_observer_microtask(DOM::Document& document) MUST(wrapped_records->create_data_property(property_index, record.ptr())); } - auto* wrapped_mutation_observer = Bindings::wrap(realm, mutation_observer); - - auto result = IDL::invoke_callback(callback, wrapped_mutation_observer, wrapped_records, wrapped_mutation_observer); + auto result = IDL::invoke_callback(callback, mutation_observer.ptr(), wrapped_records, mutation_observer.ptr()); if (result.is_abrupt()) HTML::report_exception(result); } diff --git a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.h b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.h index d3825618b6..d9f10bac27 100644 --- a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.h +++ b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.h @@ -29,7 +29,7 @@ struct WebEngineCustomData final : public JS::VM::CustomData { // https://dom.spec.whatwg.org/#mutation-observer-list // FIXME: This should be a set. - NonnullRefPtrVector mutation_observers; + Vector> mutation_observers; OwnPtr root_execution_context; diff --git a/Userland/Libraries/LibWeb/DOM/MutationObserver.cpp b/Userland/Libraries/LibWeb/DOM/MutationObserver.cpp index d7f5851dbc..74533d3248 100644 --- a/Userland/Libraries/LibWeb/DOM/MutationObserver.cpp +++ b/Userland/Libraries/LibWeb/DOM/MutationObserver.cpp @@ -5,23 +5,42 @@ */ #include +#include #include #include #include namespace Web::DOM { -// https://dom.spec.whatwg.org/#dom-mutationobserver-mutationobserver -MutationObserver::MutationObserver(HTML::Window& window_object, JS::Handle callback) - : m_callback(move(callback)) +JS::NonnullGCPtr MutationObserver::create_with_global_object(HTML::Window& window, JS::GCPtr callback) { + return *window.heap().allocate(window.realm(), window, callback); +} + +// https://dom.spec.whatwg.org/#dom-mutationobserver-mutationobserver +MutationObserver::MutationObserver(HTML::Window& window, JS::GCPtr callback) + : PlatformObject(window.realm()) + , m_callback(move(callback)) +{ + set_prototype(&window.ensure_web_prototype("MutationObserver")); + // 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()); + auto* agent_custom_data = verify_cast(window.vm().custom_data()); agent_custom_data->mutation_observers.append(*this); } +MutationObserver::~MutationObserver() = default; + +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 ExceptionOr MutationObserver::observe(Node& target, MutationObserverInit options) { @@ -55,7 +74,7 @@ ExceptionOr MutationObserver::observe(Node& target, MutationObserverInit o // 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) + if (registered_observer.observer().ptr() != this) continue; updated_existing_observer = true; @@ -67,12 +86,12 @@ ExceptionOr MutationObserver::observe(Node& target, MutationObserverInit o continue; node->registered_observers_list().remove_all_matching([®istered_observer](RegisteredObserver& observer) { - return is(observer) && verify_cast(observer).source.ptr() == ®istered_observer; + return is(observer) && verify_cast(observer).source().ptr() == ®istered_observer; }); } // 2. Set registered’s options to options. - registered_observer.options = options; + registered_observer.set_options(options); break; } @@ -99,7 +118,7 @@ void MutationObserver::disconnect() continue; node->registered_observers_list().remove_all_matching([this](RegisteredObserver& registered_observer) { - return registered_observer.observer.ptr() == this; + return registered_observer.observer().ptr() == this; }); } @@ -111,7 +130,9 @@ void MutationObserver::disconnect() Vector> MutationObserver::take_records() { // 1. Let records be a clone of this’s record queue. - auto records = m_record_queue; + Vector> records; + for (auto& record : m_record_queue) + records.append(*record); // 2. Empty this’s record queue. m_record_queue.clear(); @@ -120,4 +141,42 @@ Vector> MutationObserver::take_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()); +} + } diff --git a/Userland/Libraries/LibWeb/DOM/MutationObserver.h b/Userland/Libraries/LibWeb/DOM/MutationObserver.h index 57af34ce46..70475e4d61 100644 --- a/Userland/Libraries/LibWeb/DOM/MutationObserver.h +++ b/Userland/Libraries/LibWeb/DOM/MutationObserver.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2022, Luke Wilde + * Copyright (c) 2022, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ @@ -28,18 +29,12 @@ struct MutationObserverInit { }; // https://dom.spec.whatwg.org/#mutationobserver -class MutationObserver final - : public RefCounted - , public Bindings::Wrappable { +class MutationObserver final : public Bindings::PlatformObject { + WEB_PLATFORM_OBJECT(MutationObserver, Bindings::PlatformObject); + public: - using WrapperType = Bindings::MutationObserverWrapper; - - static NonnullRefPtr create_with_global_object(HTML::Window& window_object, Bindings::CallbackType* callback) - { - return adopt_ref(*new MutationObserver(window_object, JS::make_handle(callback))); - } - - virtual ~MutationObserver() override = default; + static JS::NonnullGCPtr create_with_global_object(HTML::Window&, JS::GCPtr); + virtual ~MutationObserver() override; ExceptionOr observe(Node& target, MutationObserverInit options = {}); void disconnect(); @@ -56,53 +51,61 @@ public: } private: - MutationObserver(HTML::Window& window_object, JS::Handle callback); + MutationObserver(HTML::Window&, JS::GCPtr); + + virtual void visit_edges(Cell::Visitor&) override; // https://dom.spec.whatwg.org/#concept-mo-callback - JS::Handle m_callback; + JS::GCPtr m_callback; // https://dom.spec.whatwg.org/#mutationobserver-node-list + // NOTE: These are weak, per https://dom.spec.whatwg.org/#garbage-collection + // Registered observers in a node’s registered observer list have a weak reference to the node. Vector> m_node_list; // https://dom.spec.whatwg.org/#concept-mo-queue - Vector> m_record_queue; + Vector> 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)); - } +class RegisteredObserver : public JS::Cell { + JS_CELL(RegisteredObserver, JS::Cell); - RegisteredObserver(MutationObserver& observer, MutationObserverInit& options) - : observer(observer) - , options(options) - { - } +public: + static JS::NonnullGCPtr create(MutationObserver&, MutationObserverInit const&); + virtual ~RegisteredObserver() override; - virtual ~RegisteredObserver() = default; + JS::NonnullGCPtr observer() const { return m_observer; } - NonnullRefPtr observer; - MutationObserverInit options; + MutationObserverInit const& options() const { return m_options; } + void set_options(MutationObserverInit options) { m_options = move(options); } + +protected: + RegisteredObserver(MutationObserver& observer, MutationObserverInit const& options); + virtual void visit_edges(Cell::Visitor&) override; + +private: + JS::NonnullGCPtr m_observer; + MutationObserverInit m_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)); - } +class TransientRegisteredObserver final : public RegisteredObserver { + JS_CELL(TransientRegisteredObserver, RegisteredObserver); - TransientRegisteredObserver(MutationObserver& observer, MutationObserverInit& options, RegisteredObserver& source) - : RegisteredObserver(observer, options) - , source(source) - { - } +public: + static JS::NonnullGCPtr create(MutationObserver&, MutationObserverInit const&, RegisteredObserver& source); + virtual ~TransientRegisteredObserver() override; - virtual ~TransientRegisteredObserver() override = default; + JS::NonnullGCPtr source() const { return m_source; } - NonnullRefPtr source; +private: + TransientRegisteredObserver(MutationObserver& observer, MutationObserverInit const& options, RegisteredObserver& source); + virtual void visit_edges(Cell::Visitor&) override; + + JS::NonnullGCPtr m_source; }; } + +WRAPPER_HACK(MutationObserver, Web::DOM) diff --git a/Userland/Libraries/LibWeb/DOM/Node.cpp b/Userland/Libraries/LibWeb/DOM/Node.cpp index d6d05efab7..1feab011fc 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.cpp +++ b/Userland/Libraries/LibWeb/DOM/Node.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -89,6 +90,9 @@ void Node::visit_edges(Cell::Visitor& visitor) visitor.visit(m_last_child.ptr()); visitor.visit(m_next_sibling.ptr()); visitor.visit(m_previous_sibling.ptr()); + + for (auto& registered_observer : m_registered_observer_list) + visitor.visit(registered_observer); } // https://dom.spec.whatwg.org/#dom-node-baseuri @@ -578,8 +582,8 @@ void Node::remove(bool suppress_observers) // whose observer is registered’s observer, options is registered’s options, and source is registered to node’s registered observer list. for (auto* inclusive_ancestor = parent; inclusive_ancestor; inclusive_ancestor = inclusive_ancestor->parent()) { for (auto& registered : inclusive_ancestor->m_registered_observer_list) { - if (registered.options.subtree) { - auto transient_observer = TransientRegisteredObserver::create(registered.observer, registered.options, registered); + if (registered.options().subtree) { + auto transient_observer = TransientRegisteredObserver::create(registered.observer(), registered.options(), registered); m_registered_observer_list.append(move(transient_observer)); } } @@ -1321,9 +1325,13 @@ Painting::PaintableBox const* Node::paint_box() const // https://dom.spec.whatwg.org/#queue-a-mutation-record void Node::queue_mutation_record(FlyString const& type, String attribute_name, String attribute_namespace, String old_value, JS::NonnullGCPtr added_nodes, JS::NonnullGCPtr removed_nodes, Node* previous_sibling, Node* next_sibling) { + // NOTE: We defer garbage collection until the end of the scope, since we can't safely use MutationObserver* as a hashmap key otherwise. + // FIXME: This is a total hack. + JS::DeferGC defer_gc(heap()); + // 1. Let interestedObservers be an empty map. // mutationObserver -> mappedOldValue - OrderedHashMap, String> interested_observers; + OrderedHashMap interested_observers; // 2. Let nodes be the inclusive ancestors of target. Vector> nodes; @@ -1336,7 +1344,7 @@ void Node::queue_mutation_record(FlyString const& type, String attribute_name, S for (auto& node : nodes) { for (auto& registered_observer : node->m_registered_observer_list) { // 1. Let options be registered’s options. - auto& options = registered_observer.options; + auto& options = registered_observer.options(); // 2. If none of the following are true // - node is not target and options["subtree"] is false @@ -1351,7 +1359,7 @@ void Node::queue_mutation_record(FlyString const& type, String attribute_name, S && !(type == MutationType::characterData && (!options.character_data.has_value() || !options.character_data.value())) && !(type == MutationType::childList && !options.child_list)) { // 1. Let mo be registered’s observer. - auto mutation_observer = registered_observer.observer; + auto mutation_observer = registered_observer.observer(); // 2. If interestedObservers[mo] does not exist, then set interestedObservers[mo] to null. if (!interested_observers.contains(mutation_observer)) diff --git a/Userland/Libraries/LibWeb/DOM/Node.h b/Userland/Libraries/LibWeb/DOM/Node.h index 87d58e79e0..168b3dee2f 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.h +++ b/Userland/Libraries/LibWeb/DOM/Node.h @@ -15,7 +15,6 @@ #include #include #include -#include namespace Web::DOM { @@ -217,8 +216,8 @@ 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; } + auto& registered_observers_list() { return m_registered_observer_list; } + auto const& registered_observers_list() const { return m_registered_observer_list; } void add_registered_observer(RegisteredObserver& registered_observer) { m_registered_observer_list.append(registered_observer); } @@ -638,7 +637,7 @@ protected: // 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; + Vector m_registered_observer_list; private: void queue_tree_mutation_record(JS::NonnullGCPtr added_nodes, JS::NonnullGCPtr removed_nodes, Node* previous_sibling, Node* next_sibling); diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 7778d40efd..954c385367 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -158,6 +158,7 @@ class ParentNode; class Position; class ProcessingInstruction; class Range; +class RegisteredObserver; class ShadowRoot; class StaticNodeList; class StaticRange; @@ -469,7 +470,6 @@ class ImageDataWrapper; class IntersectionObserverWrapper; class LocationObject; class MessageChannelWrapper; -class MutationObserverWrapper; class OptionConstructor; class Path2DWrapper; class RangePrototype; diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index 012dea5d09..6574bade9f 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -39,7 +39,7 @@ libweb_js_wrapper(DOM/Event NO_INSTANCE) libweb_js_wrapper(DOM/EventTarget NO_INSTANCE) libweb_js_wrapper(DOM/HTMLCollection) libweb_js_wrapper(DOM/MutationRecord NO_INSTANCE) -libweb_js_wrapper(DOM/MutationObserver) +libweb_js_wrapper(DOM/MutationObserver NO_INSTANCE) libweb_js_wrapper(DOM/NamedNodeMap NO_INSTANCE) libweb_js_wrapper(DOM/Node NO_INSTANCE) libweb_js_wrapper(DOM/NodeIterator NO_INSTANCE)