1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 16:57:35 +00:00

LibWeb: Make MutationObserver GC-allocated

This commit is contained in:
Andreas Kling 2022-09-01 17:59:48 +02:00
parent 43ec0f734f
commit 905eb8cb4d
8 changed files with 134 additions and 68 deletions

View file

@ -14,7 +14,6 @@
#include <LibWeb/Bindings/IDLAbstractOperations.h>
#include <LibWeb/Bindings/LocationObject.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Bindings/MutationObserverWrapper.h>
#include <LibWeb/Bindings/WindowProxy.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/PromiseRejectionEvent.h>
@ -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 mos record queue.
// 2. Empty mos record queue.
auto records = mutation_observer.take_records();
auto records = mutation_observer->take_records();
// 3. For each node of mos node list, remove all transient registered observers whose observer is mo from nodes 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<DOM::TransientRegisteredObserver>(registered_observer) && static_cast<DOM::TransientRegisteredObserver&>(registered_observer).observer.ptr() == &mutation_observer;
return is<DOM::TransientRegisteredObserver>(registered_observer) && static_cast<DOM::TransientRegisteredObserver&>(registered_observer).observer().ptr() == mutation_observer.ptr();
});
}
// 4. If records is not empty, then invoke mos 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);
}

View file

@ -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<DOM::MutationObserver> mutation_observers;
Vector<JS::Handle<DOM::MutationObserver>> mutation_observers;
OwnPtr<JS::ExecutionContext> root_execution_context;

View file

@ -5,23 +5,42 @@
*/
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Bindings/MutationObserverPrototype.h>
#include <LibWeb/DOM/MutationObserver.h>
#include <LibWeb/DOM/Node.h>
#include <LibWeb/HTML/Window.h>
namespace Web::DOM {
// https://dom.spec.whatwg.org/#dom-mutationobserver-mutationobserver
MutationObserver::MutationObserver(HTML::Window& window_object, JS::Handle<Bindings::CallbackType> callback)
: m_callback(move(callback))
JS::NonnullGCPtr<MutationObserver> MutationObserver::create_with_global_object(HTML::Window& window, JS::GCPtr<Bindings::CallbackType> callback)
{
return *window.heap().allocate<MutationObserver>(window.realm(), window, callback);
}
// https://dom.spec.whatwg.org/#dom-mutationobserver-mutationobserver
MutationObserver::MutationObserver(HTML::Window& window, JS::GCPtr<Bindings::CallbackType> callback)
: PlatformObject(window.realm())
, m_callback(move(callback))
{
set_prototype(&window.ensure_web_prototype<Bindings::MutationObserverPrototype>("MutationObserver"));
// 1. Set thiss callback to callback.
// 2. Append this to thiss relevant agents mutation observers.
auto* agent_custom_data = verify_cast<Bindings::WebEngineCustomData>(window_object.vm().custom_data());
auto* agent_custom_data = verify_cast<Bindings::WebEngineCustomData>(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<void> MutationObserver::observe(Node& target, MutationObserverInit options)
{
@ -55,7 +74,7 @@ ExceptionOr<void> MutationObserver::observe(Node& target, MutationObserverInit o
// 7. For each registered of targets registered observer list, if registereds 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<void> MutationObserver::observe(Node& target, MutationObserverInit o
continue;
node->registered_observers_list().remove_all_matching([&registered_observer](RegisteredObserver& observer) {
return is<TransientRegisteredObserver>(observer) && verify_cast<TransientRegisteredObserver>(observer).source.ptr() == &registered_observer;
return is<TransientRegisteredObserver>(observer) && verify_cast<TransientRegisteredObserver>(observer).source().ptr() == &registered_observer;
});
}
// 2. Set registereds 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<JS::Handle<MutationRecord>> MutationObserver::take_records()
{
// 1. Let records be a clone of thiss record queue.
auto records = m_record_queue;
Vector<JS::Handle<MutationRecord>> records;
for (auto& record : m_record_queue)
records.append(*record);
// 2. Empty thiss record queue.
m_record_queue.clear();
@ -120,4 +141,42 @@ Vector<JS::Handle<MutationRecord>> MutationObserver::take_records()
return records;
}
JS::NonnullGCPtr<RegisteredObserver> RegisteredObserver::create(MutationObserver& observer, MutationObserverInit const& options)
{
return *observer.heap().allocate_without_realm<RegisteredObserver>(observer, options);
}
RegisteredObserver::RegisteredObserver(MutationObserver& observer, MutationObserverInit const& options)
: m_observer(observer)
, m_options(options)
{
}
RegisteredObserver::~RegisteredObserver() = default;
void RegisteredObserver::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_observer.ptr());
}
JS::NonnullGCPtr<TransientRegisteredObserver> TransientRegisteredObserver::create(MutationObserver& observer, MutationObserverInit const& options, RegisteredObserver& source)
{
return *observer.heap().allocate_without_realm<TransientRegisteredObserver>(observer, options, source);
}
TransientRegisteredObserver::TransientRegisteredObserver(MutationObserver& observer, MutationObserverInit const& options, RegisteredObserver& source)
: RegisteredObserver(observer, options)
, m_source(source)
{
}
TransientRegisteredObserver::~TransientRegisteredObserver() = default;
void TransientRegisteredObserver::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_source.ptr());
}
}

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -28,18 +29,12 @@ struct MutationObserverInit {
};
// https://dom.spec.whatwg.org/#mutationobserver
class MutationObserver final
: public RefCounted<MutationObserver>
, public Bindings::Wrappable {
class MutationObserver final : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(MutationObserver, Bindings::PlatformObject);
public:
using WrapperType = Bindings::MutationObserverWrapper;
static NonnullRefPtr<MutationObserver> 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<MutationObserver> create_with_global_object(HTML::Window&, JS::GCPtr<Bindings::CallbackType>);
virtual ~MutationObserver() override;
ExceptionOr<void> observe(Node& target, MutationObserverInit options = {});
void disconnect();
@ -56,53 +51,61 @@ public:
}
private:
MutationObserver(HTML::Window& window_object, JS::Handle<Bindings::CallbackType> callback);
MutationObserver(HTML::Window&, JS::GCPtr<Bindings::CallbackType>);
virtual void visit_edges(Cell::Visitor&) override;
// https://dom.spec.whatwg.org/#concept-mo-callback
JS::Handle<Bindings::CallbackType> m_callback;
JS::GCPtr<Bindings::CallbackType> 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 nodes registered observer list have a weak reference to the node.
Vector<WeakPtr<Node>> m_node_list;
// https://dom.spec.whatwg.org/#concept-mo-queue
Vector<JS::Handle<MutationRecord>> m_record_queue;
Vector<JS::NonnullGCPtr<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));
}
class RegisteredObserver : public JS::Cell {
JS_CELL(RegisteredObserver, JS::Cell);
RegisteredObserver(MutationObserver& observer, MutationObserverInit& options)
: observer(observer)
, options(options)
{
}
public:
static JS::NonnullGCPtr<RegisteredObserver> create(MutationObserver&, MutationObserverInit const&);
virtual ~RegisteredObserver() override;
virtual ~RegisteredObserver() = default;
JS::NonnullGCPtr<MutationObserver> observer() const { return m_observer; }
NonnullRefPtr<MutationObserver> 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<MutationObserver> m_observer;
MutationObserverInit m_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));
}
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<TransientRegisteredObserver> create(MutationObserver&, MutationObserverInit const&, RegisteredObserver& source);
virtual ~TransientRegisteredObserver() override;
virtual ~TransientRegisteredObserver() override = default;
JS::NonnullGCPtr<RegisteredObserver> source() const { return m_source; }
NonnullRefPtr<RegisteredObserver> source;
private:
TransientRegisteredObserver(MutationObserver& observer, MutationObserverInit const& options, RegisteredObserver& source);
virtual void visit_edges(Cell::Visitor&) override;
JS::NonnullGCPtr<RegisteredObserver> m_source;
};
}
WRAPPER_HACK(MutationObserver, Web::DOM)

View file

@ -9,6 +9,7 @@
#include <AK/IDAllocator.h>
#include <AK/StringBuilder.h>
#include <LibJS/AST.h>
#include <LibJS/Heap/DeferGC.h>
#include <LibJS/Runtime/FunctionObject.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Bindings/NodePrototype.h>
@ -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 registereds observer, options is registereds options, and source is registered to nodes 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<NodeList> added_nodes, JS::NonnullGCPtr<NodeList> 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<NonnullRefPtr<MutationObserver>, String> interested_observers;
OrderedHashMap<MutationObserver*, String> interested_observers;
// 2. Let nodes be the inclusive ancestors of target.
Vector<JS::Handle<Node>> 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 registereds 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 registereds 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))

View file

@ -15,7 +15,6 @@
#include <LibWeb/Bindings/Wrappable.h>
#include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/DOM/ExceptionOr.h>
#include <LibWeb/DOM/MutationObserver.h>
namespace Web::DOM {
@ -217,8 +216,8 @@ 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; }
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<RegisteredObserver> m_registered_observer_list;
Vector<RegisteredObserver&> m_registered_observer_list;
private:
void queue_tree_mutation_record(JS::NonnullGCPtr<NodeList> added_nodes, JS::NonnullGCPtr<NodeList> removed_nodes, Node* previous_sibling, Node* next_sibling);

View file

@ -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;

View file

@ -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)