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:
parent
43ec0f734f
commit
905eb8cb4d
8 changed files with 134 additions and 68 deletions
|
@ -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 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<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 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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 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());
|
||||
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 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<void> MutationObserver::observe(Node& target, MutationObserverInit o
|
|||
continue;
|
||||
|
||||
node->registered_observers_list().remove_all_matching([®istered_observer](RegisteredObserver& observer) {
|
||||
return is<TransientRegisteredObserver>(observer) && verify_cast<TransientRegisteredObserver>(observer).source.ptr() == ®istered_observer;
|
||||
return is<TransientRegisteredObserver>(observer) && verify_cast<TransientRegisteredObserver>(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<JS::Handle<MutationRecord>> MutationObserver::take_records()
|
||||
{
|
||||
// 1. Let records be a clone of this’s record queue.
|
||||
auto records = m_record_queue;
|
||||
Vector<JS::Handle<MutationRecord>> 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<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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 node’s 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)
|
||||
|
|
|
@ -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 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<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 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))
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue