1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-06-01 12:08:14 +00:00

LibWeb: Make NodeList GC-allocated

This commit is contained in:
Andreas Kling 2022-09-01 16:30:26 +02:00
parent 8f4ea4e308
commit 48e0066371
16 changed files with 128 additions and 57 deletions

View file

@ -104,6 +104,7 @@ set(SOURCES
DOM/Node.cpp
DOM/NodeFilter.cpp
DOM/NodeIterator.cpp
DOM/NodeList.cpp
DOM/NodeOperations.cpp
DOM/ParentNode.cpp
DOM/Position.cpp

View file

@ -70,7 +70,7 @@ void Attribute::set_value(String value)
void Attribute::handle_attribute_changes(Element& element, String const& old_value, [[maybe_unused]] String const& new_value)
{
// 1. Queue a mutation record of "attributes" for element with attributes local name, attributes namespace, oldValue, « », « », null, and null.
element.queue_mutation_record(MutationType::attributes, local_name(), namespace_uri(), old_value, StaticNodeList::create({}), StaticNodeList::create({}), nullptr, nullptr);
element.queue_mutation_record(MutationType::attributes, local_name(), namespace_uri(), old_value, StaticNodeList::create(window(), {}), StaticNodeList::create(window(), {}), nullptr, nullptr);
// FIXME: 2. If element is custom, then enqueue a custom element callback reaction with element, callback name "attributeChangedCallback", and an argument list containing attributes local name, oldValue, newValue, and attributes namespace.

View file

@ -64,7 +64,7 @@ ExceptionOr<void> CharacterData::replace_data(size_t offset, size_t count, Strin
count = length - offset;
// 4. Queue a mutation record of "characterData" for node with null, null, nodes data, « », « », null, and null.
queue_mutation_record(MutationType::characterData, {}, {}, m_data, StaticNodeList::create({}), StaticNodeList::create({}), nullptr, nullptr);
queue_mutation_record(MutationType::characterData, {}, {}, m_data, StaticNodeList::create(window(), {}), StaticNodeList::create(window(), {}), nullptr, nullptr);
// 5. Insert data into nodes data after offset code units.
// 6. Let delete offset be offset + datas length.

View file

@ -1,23 +1,39 @@
/*
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/DOM/LiveNodeList.h>
#include <LibWeb/DOM/Node.h>
#include <LibWeb/HTML/Window.h>
namespace Web::DOM {
LiveNodeList::LiveNodeList(Node& root, Function<bool(Node const&)> filter)
: m_root(JS::make_handle(root))
JS::NonnullGCPtr<NodeList> LiveNodeList::create(HTML::Window& window, Node& root, Function<bool(Node const&)> filter)
{
return *window.heap().allocate<LiveNodeList>(window.realm(), window, root, move(filter));
}
LiveNodeList::LiveNodeList(HTML::Window& window, Node& root, Function<bool(Node const&)> filter)
: NodeList(window)
, m_root(root)
, m_filter(move(filter))
{
}
LiveNodeList::~LiveNodeList() = default;
void LiveNodeList::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_root.ptr());
}
JS::MarkedVector<Node*> LiveNodeList::collection() const
{
JS::MarkedVector<Node*> nodes(m_root->heap());
JS::MarkedVector<Node*> nodes(heap());
m_root->for_each_in_inclusive_subtree([&](auto& node) {
if (m_filter(node))
nodes.append(const_cast<Node*>(&node));

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,19 +8,18 @@
#pragma once
#include <AK/Function.h>
#include <AK/NonnullRefPtrVector.h>
#include <LibWeb/DOM/NodeList.h>
namespace Web::DOM {
// FIXME: Just like HTMLCollection, LiveNodeList currently does no caching.
class LiveNodeList : public NodeList {
class LiveNodeList final : public NodeList {
WEB_PLATFORM_OBJECT(LiveNodeList, NodeList);
public:
static NonnullRefPtr<NodeList> create(Node& root, Function<bool(Node const&)> filter)
{
return adopt_ref(*new LiveNodeList(root, move(filter)));
}
static JS::NonnullGCPtr<NodeList> create(HTML::Window&, Node& root, Function<bool(Node const&)> filter);
virtual ~LiveNodeList() override;
virtual u32 length() const override;
virtual Node const* item(u32 index) const override;
@ -27,11 +27,13 @@ public:
virtual bool is_supported_property_index(u32) const override;
private:
LiveNodeList(Node& root, Function<bool(Node const&)> filter);
LiveNodeList(HTML::Window&, Node& root, Function<bool(Node const&)> filter);
virtual void visit_edges(Cell::Visitor&) override;
JS::MarkedVector<Node*> collection() const;
JS::Handle<Node> m_root;
JS::NonnullGCPtr<Node> m_root;
Function<bool(Node const&)> m_filter;
};

View file

@ -40,8 +40,8 @@ public:
private:
FlyString m_type;
JS::Handle<Node> m_target;
NonnullRefPtr<NodeList> m_added_nodes;
NonnullRefPtr<NodeList> m_removed_nodes;
JS::Handle<NodeList> m_added_nodes;
JS::Handle<NodeList> m_removed_nodes;
JS::Handle<Node> m_previous_sibling;
JS::Handle<Node> m_next_sibling;
String m_attribute_name;

View file

@ -376,7 +376,7 @@ void Node::insert_before(JS::NonnullGCPtr<Node> node, JS::GCPtr<Node> child, boo
// 2. Queue a tree mutation record for node with « », nodes, null, and null.
// NOTE: This step intentionally does not pay attention to the suppress observers flag.
node->queue_tree_mutation_record(StaticNodeList::create({}), StaticNodeList::create(nodes), nullptr, nullptr);
node->queue_tree_mutation_record(StaticNodeList::create(window(), {}), StaticNodeList::create(window(), nodes), nullptr, nullptr);
}
// 5. If child is non-null, then:
@ -438,7 +438,7 @@ void Node::insert_before(JS::NonnullGCPtr<Node> node, JS::GCPtr<Node> child, boo
// 8. If suppress observers flag is unset, then queue a tree mutation record for parent with nodes, « », previousSibling, and child.
if (!suppress_observers)
queue_tree_mutation_record(StaticNodeList::create(move(nodes)), StaticNodeList::create({}), previous_sibling.ptr(), child.ptr());
queue_tree_mutation_record(StaticNodeList::create(window(), move(nodes)), StaticNodeList::create(window(), {}), previous_sibling.ptr(), child.ptr());
// 9. Run the children changed steps for parent.
children_changed();
@ -589,7 +589,7 @@ void Node::remove(bool suppress_observers)
if (!suppress_observers) {
Vector<JS::Handle<Node>> removed_nodes;
removed_nodes.append(JS::make_handle(*this));
parent->queue_tree_mutation_record(StaticNodeList::create({}), StaticNodeList::create(move(removed_nodes)), old_previous_sibling.ptr(), old_next_sibling.ptr());
parent->queue_tree_mutation_record(StaticNodeList::create(window(), {}), StaticNodeList::create(window(), move(removed_nodes)), old_previous_sibling.ptr(), old_next_sibling.ptr());
}
// 21. Run the children changed steps for parent.
@ -681,7 +681,7 @@ ExceptionOr<JS::NonnullGCPtr<Node>> Node::replace_child(JS::NonnullGCPtr<Node> n
insert_before(node, reference_child, true);
// 14. Queue a tree mutation record for parent with nodes, removedNodes, previousSibling, and referenceChild.
queue_tree_mutation_record(StaticNodeList::create(move(nodes)), StaticNodeList::create(move(removed_nodes)), previous_sibling.ptr(), reference_child.ptr());
queue_tree_mutation_record(StaticNodeList::create(window(), move(nodes)), StaticNodeList::create(window(), move(removed_nodes)), previous_sibling.ptr(), reference_child.ptr());
// 15. Return child.
return child;
@ -851,11 +851,11 @@ ParentNode* Node::parent_or_shadow_host()
return verify_cast<ParentNode>(parent());
}
NonnullRefPtr<NodeList> Node::child_nodes()
JS::NonnullGCPtr<NodeList> Node::child_nodes()
{
// FIXME: This should return the same LiveNodeList object every time,
// but that would cause a reference cycle since NodeList refs the root.
return LiveNodeList::create(*this, [this](auto& node) {
return LiveNodeList::create(window(), *this, [this](auto& node) {
return is_parent_of(node);
});
}
@ -1133,7 +1133,7 @@ void Node::replace_all(JS::GCPtr<Node> node)
// 7. If either addedNodes or removedNodes is not empty, then queue a tree mutation record for parent with addedNodes, removedNodes, null, and null.
if (!added_nodes.is_empty() || !removed_nodes.is_empty())
queue_tree_mutation_record(StaticNodeList::create(move(added_nodes)), StaticNodeList::create(move(removed_nodes)), nullptr, nullptr);
queue_tree_mutation_record(StaticNodeList::create(window(), move(added_nodes)), StaticNodeList::create(window(), move(removed_nodes)), nullptr, nullptr);
}
// https://dom.spec.whatwg.org/#string-replace-all
@ -1319,7 +1319,7 @@ 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, NonnullRefPtr<NodeList> added_nodes, NonnullRefPtr<NodeList> removed_nodes, Node* previous_sibling, Node* next_sibling)
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)
{
// 1. Let interestedObservers be an empty map.
// mutationObserver -> mappedOldValue
@ -1379,7 +1379,7 @@ void Node::queue_mutation_record(FlyString const& type, String attribute_name, S
}
// https://dom.spec.whatwg.org/#queue-a-tree-mutation-record
void Node::queue_tree_mutation_record(NonnullRefPtr<NodeList> added_nodes, NonnullRefPtr<NodeList> removed_nodes, Node* previous_sibling, Node* next_sibling)
void Node::queue_tree_mutation_record(JS::NonnullGCPtr<NodeList> added_nodes, JS::NonnullGCPtr<NodeList> removed_nodes, Node* previous_sibling, Node* next_sibling)
{
// 1. Assert: either addedNodes or removedNodes is not empty.
VERIFY(added_nodes->length() > 0 || removed_nodes->length() > 0);
@ -1483,4 +1483,9 @@ bool Node::is_following(Node const& other) const
return false;
}
HTML::Window& Node::window() const
{
return document().window();
}
}

View file

@ -101,7 +101,7 @@ public:
// NOTE: This is intended for the JS bindings.
bool has_child_nodes() const { return has_children(); }
NonnullRefPtr<NodeList> child_nodes();
JS::NonnullGCPtr<NodeList> child_nodes();
Vector<JS::Handle<Node>> children_as_vector() const;
virtual FlyString node_name() const = 0;
@ -118,6 +118,8 @@ public:
Document& document() { return *m_document; }
Document const& document() const { return *m_document; }
HTML::Window& window() const;
JS::GCPtr<Document> owner_document() const;
const HTML::HTMLAnchorElement* enclosing_link_element() const;
@ -220,7 +222,7 @@ public:
void add_registered_observer(RegisteredObserver& registered_observer) { m_registered_observer_list.append(registered_observer); }
void queue_mutation_record(FlyString const& type, String attribute_name, String attribute_namespace, String old_value, NonnullRefPtr<NodeList> added_nodes, NonnullRefPtr<NodeList> removed_nodes, Node* previous_sibling, Node* next_sibling);
void 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);
// https://dom.spec.whatwg.org/#concept-shadow-including-descendant
template<typename Callback>
@ -639,7 +641,7 @@ protected:
NonnullRefPtrVector<RegisteredObserver> m_registered_observer_list;
private:
void queue_tree_mutation_record(NonnullRefPtr<NodeList> added_nodes, NonnullRefPtr<NodeList> removed_nodes, Node* previous_sibling, Node* next_sibling);
void queue_tree_mutation_record(JS::NonnullGCPtr<NodeList> added_nodes, JS::NonnullGCPtr<NodeList> removed_nodes, Node* previous_sibling, Node* next_sibling);
void insert_before_impl(JS::NonnullGCPtr<Node>, JS::GCPtr<Node> child);
void append_child_impl(JS::NonnullGCPtr<Node>);

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/NodeListPrototype.h>
#include <LibWeb/DOM/Node.h>
#include <LibWeb/DOM/NodeList.h>
#include <LibWeb/HTML/Window.h>
namespace Web::DOM {
NodeList::NodeList(HTML::Window& window)
: LegacyPlatformObject(window.ensure_web_prototype<Bindings::NodeListPrototype>("NodeList"))
{
}
NodeList::~NodeList() = default;
JS::Value NodeList::item_value(size_t index) const
{
auto* node = item(index);
if (!node)
return JS::js_undefined();
return const_cast<Node*>(node);
}
bool NodeList::is_supported_property_index(u32 index) const
{
return index < length();
}
}

View file

@ -1,37 +1,33 @@
/*
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Noncopyable.h>
#include <AK/RefCounted.h>
#include <LibWeb/Bindings/Wrappable.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Bindings/LegacyPlatformObject.h>
namespace Web::DOM {
// https://dom.spec.whatwg.org/#nodelist
class NodeList
: public RefCounted<NodeList>
, public Bindings::Wrappable {
AK_MAKE_NONCOPYABLE(NodeList);
AK_MAKE_NONMOVABLE(NodeList);
class NodeList : public Bindings::LegacyPlatformObject {
WEB_PLATFORM_OBJECT(NodeList, Bindings::LegacyPlatformObject);
public:
using WrapperType = Bindings::NodeListWrapper;
virtual ~NodeList() override = default;
virtual ~NodeList() override;
virtual u32 length() const = 0;
virtual Node const* item(u32 index) const = 0;
virtual bool is_supported_property_index(u32) const = 0;
virtual JS::Value item_value(size_t index) const override;
virtual bool is_supported_property_index(u32) const override;
protected:
NodeList() = default;
explicit NodeList(HTML::Window&);
};
}
WRAPPER_HACK(NodeList, Web::DOM)

View file

@ -38,7 +38,7 @@ ExceptionOr<JS::GCPtr<Element>> ParentNode::query_selector(StringView selector_t
return result;
}
ExceptionOr<NonnullRefPtr<NodeList>> ParentNode::query_selector_all(StringView selector_text)
ExceptionOr<JS::NonnullGCPtr<NodeList>> ParentNode::query_selector_all(StringView selector_text)
{
auto maybe_selectors = parse_selector(CSS::Parser::ParsingContext(*this), selector_text);
if (!maybe_selectors.has_value())
@ -57,7 +57,7 @@ ExceptionOr<NonnullRefPtr<NodeList>> ParentNode::query_selector_all(StringView s
return IterationDecision::Continue;
});
return StaticNodeList::create(move(elements));
return StaticNodeList::create(window(), move(elements));
}
JS::GCPtr<Element> ParentNode::first_element_child()

View file

@ -24,7 +24,7 @@ public:
u32 child_element_count() const;
ExceptionOr<JS::GCPtr<Element>> query_selector(StringView);
ExceptionOr<NonnullRefPtr<NodeList>> query_selector_all(StringView);
ExceptionOr<JS::NonnullGCPtr<NodeList>> query_selector_all(StringView);
NonnullRefPtr<HTMLCollection> children();

View file

@ -5,17 +5,29 @@
*/
#include <LibWeb/DOM/StaticNodeList.h>
#include <LibWeb/HTML/Window.h>
namespace Web::DOM {
NonnullRefPtr<NodeList> StaticNodeList::create(Vector<JS::Handle<Node>> static_nodes)
JS::NonnullGCPtr<NodeList> StaticNodeList::create(HTML::Window& window, Vector<JS::Handle<Node>> static_nodes)
{
return adopt_ref(*new StaticNodeList(move(static_nodes)));
return *window.heap().allocate<StaticNodeList>(window.realm(), window, move(static_nodes));
}
StaticNodeList::StaticNodeList(Vector<JS::Handle<Node>> static_nodes)
: m_static_nodes(move(static_nodes))
StaticNodeList::StaticNodeList(HTML::Window& window, Vector<JS::Handle<Node>> static_nodes)
: NodeList(window)
{
for (auto& node : static_nodes)
m_static_nodes.append(*node);
}
StaticNodeList::~StaticNodeList() = default;
void StaticNodeList::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
for (auto& node : m_static_nodes)
visitor.visit(node);
}
// https://dom.spec.whatwg.org/#dom-nodelist-length
@ -30,7 +42,7 @@ Node const* StaticNodeList::item(u32 index) const
// The item(index) method must return the indexth node in the collection. If there is no indexth node in the collection, then the method must return null.
if (index >= m_static_nodes.size())
return nullptr;
return m_static_nodes[index].ptr();
return &m_static_nodes[index];
}
// https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-indices

View file

@ -12,11 +12,13 @@
namespace Web::DOM {
class StaticNodeList : public NodeList {
public:
static NonnullRefPtr<NodeList> create(Vector<JS::Handle<Node>> static_nodes);
class StaticNodeList final : public NodeList {
WEB_PLATFORM_OBJECT(StaticNodeList, NodeList);
virtual ~StaticNodeList() override = default;
public:
static JS::NonnullGCPtr<NodeList> create(HTML::Window&, Vector<JS::Handle<Node>>);
virtual ~StaticNodeList() override;
virtual u32 length() const override;
virtual Node const* item(u32 index) const override;
@ -24,9 +26,11 @@ public:
virtual bool is_supported_property_index(u32) const override;
private:
StaticNodeList(Vector<JS::Handle<Node>> static_nodes);
StaticNodeList(HTML::Window&, Vector<JS::Handle<Node>>);
Vector<JS::Handle<Node>> m_static_nodes;
virtual void visit_edges(Cell::Visitor&) override;
Vector<Node&> m_static_nodes;
};
}

View file

@ -471,7 +471,6 @@ class LocationObject;
class MessageChannelWrapper;
class MutationObserverWrapper;
class MutationRecordWrapper;
class NodeListWrapper;
class OptionConstructor;
class Path2DWrapper;
class RangePrototype;

View file

@ -43,7 +43,7 @@ libweb_js_wrapper(DOM/MutationObserver)
libweb_js_wrapper(DOM/NamedNodeMap NO_INSTANCE)
libweb_js_wrapper(DOM/Node NO_INSTANCE)
libweb_js_wrapper(DOM/NodeIterator NO_INSTANCE)
libweb_js_wrapper(DOM/NodeList)
libweb_js_wrapper(DOM/NodeList NO_INSTANCE)
libweb_js_wrapper(DOM/ProcessingInstruction NO_INSTANCE)
libweb_js_wrapper(DOM/Range NO_INSTANCE)
libweb_js_wrapper(DOM/ShadowRoot NO_INSTANCE)