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

LibWeb: Introduce Mutation{Record,Observer} and observer microtasks

This commit is contained in:
Luke Wilde 2022-07-11 16:37:51 +01:00 committed by Andreas Kling
parent 116a7b74fe
commit c9ba5531e0
15 changed files with 531 additions and 0 deletions

View file

@ -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 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());
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 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)
continue;
updated_existing_observer = true;
// 1. For each node of thiss node list, remove all transient registered observers whose source is registered from nodes 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([&registered_observer](RegisteredObserver& observer) {
return is<TransientRegisteredObserver>(observer) && verify_cast<TransientRegisteredObserver>(observer).source.ptr() == &registered_observer;
});
}
// 2. Set registereds 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 targets registered observer list.
auto new_registered_observer = RegisteredObserver::create(*this, options);
target.add_registered_observer(new_registered_observer);
// 2. Append target to thiss 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 thiss node list, remove any registered observer from nodes 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 thiss 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 thiss record queue.
auto records = m_record_queue;
// 2. Empty thiss record queue.
m_record_queue.clear();
// 3. Return records.
return records;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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