1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 16:37: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/IDLAbstractOperations.h>
#include <LibWeb/Bindings/LocationObject.h> #include <LibWeb/Bindings/LocationObject.h>
#include <LibWeb/Bindings/MainThreadVM.h> #include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Bindings/MutationObserverWrapper.h>
#include <LibWeb/Bindings/WindowProxy.h> #include <LibWeb/Bindings/WindowProxy.h>
#include <LibWeb/DOM/Document.h> #include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/PromiseRejectionEvent.h> #include <LibWeb/HTML/PromiseRejectionEvent.h>
@ -356,22 +355,22 @@ void queue_mutation_observer_microtask(DOM::Document& document)
for (auto& mutation_observer : notify_set) { for (auto& mutation_observer : notify_set) {
// 1. Let records be a clone of mos record queue. // 1. Let records be a clone of mos record queue.
// 2. Empty 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. // 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? // FIXME: Is this correct?
if (node.is_null()) if (node.is_null())
continue; continue;
node->registered_observers_list().remove_all_matching([&mutation_observer](DOM::RegisteredObserver& registered_observer) { 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. // 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()) { if (!records.is_empty()) {
auto& callback = mutation_observer.callback(); auto& callback = mutation_observer->callback();
auto& realm = callback.callback_context.realm(); auto& realm = callback.callback_context.realm();
auto* wrapped_records = MUST(JS::Array::create(realm, 0)); 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())); 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, mutation_observer.ptr(), wrapped_records, mutation_observer.ptr());
auto result = IDL::invoke_callback(callback, wrapped_mutation_observer, wrapped_records, wrapped_mutation_observer);
if (result.is_abrupt()) if (result.is_abrupt())
HTML::report_exception(result); 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 // https://dom.spec.whatwg.org/#mutation-observer-list
// FIXME: This should be a set. // FIXME: This should be a set.
NonnullRefPtrVector<DOM::MutationObserver> mutation_observers; Vector<JS::Handle<DOM::MutationObserver>> mutation_observers;
OwnPtr<JS::ExecutionContext> root_execution_context; OwnPtr<JS::ExecutionContext> root_execution_context;

View file

@ -5,23 +5,42 @@
*/ */
#include <LibWeb/Bindings/MainThreadVM.h> #include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Bindings/MutationObserverPrototype.h>
#include <LibWeb/DOM/MutationObserver.h> #include <LibWeb/DOM/MutationObserver.h>
#include <LibWeb/DOM/Node.h> #include <LibWeb/DOM/Node.h>
#include <LibWeb/HTML/Window.h> #include <LibWeb/HTML/Window.h>
namespace Web::DOM { namespace Web::DOM {
// https://dom.spec.whatwg.org/#dom-mutationobserver-mutationobserver JS::NonnullGCPtr<MutationObserver> MutationObserver::create_with_global_object(HTML::Window& window, JS::GCPtr<Bindings::CallbackType> callback)
MutationObserver::MutationObserver(HTML::Window& window_object, JS::Handle<Bindings::CallbackType> callback)
: m_callback(move(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. // 1. Set thiss callback to callback.
// 2. Append this to thiss relevant agents mutation observers. // 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); 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 // https://dom.spec.whatwg.org/#dom-mutationobserver-observe
ExceptionOr<void> MutationObserver::observe(Node& target, MutationObserverInit options) 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: // 7. For each registered of targets registered observer list, if registereds observer is this:
bool updated_existing_observer = false; bool updated_existing_observer = false;
for (auto& registered_observer : target.registered_observers_list()) { for (auto& registered_observer : target.registered_observers_list()) {
if (registered_observer.observer.ptr() != this) if (registered_observer.observer().ptr() != this)
continue; continue;
updated_existing_observer = true; updated_existing_observer = true;
@ -67,12 +86,12 @@ ExceptionOr<void> MutationObserver::observe(Node& target, MutationObserverInit o
continue; continue;
node->registered_observers_list().remove_all_matching([&registered_observer](RegisteredObserver& observer) { 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. // 2. Set registereds options to options.
registered_observer.options = options; registered_observer.set_options(options);
break; break;
} }
@ -99,7 +118,7 @@ void MutationObserver::disconnect()
continue; continue;
node->registered_observers_list().remove_all_matching([this](RegisteredObserver& registered_observer) { 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() Vector<JS::Handle<MutationRecord>> MutationObserver::take_records()
{ {
// 1. Let records be a clone of thiss record queue. // 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. // 2. Empty thiss record queue.
m_record_queue.clear(); m_record_queue.clear();
@ -120,4 +141,42 @@ Vector<JS::Handle<MutationRecord>> MutationObserver::take_records()
return 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, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -28,18 +29,12 @@ struct MutationObserverInit {
}; };
// https://dom.spec.whatwg.org/#mutationobserver // https://dom.spec.whatwg.org/#mutationobserver
class MutationObserver final class MutationObserver final : public Bindings::PlatformObject {
: public RefCounted<MutationObserver> WEB_PLATFORM_OBJECT(MutationObserver, Bindings::PlatformObject);
, public Bindings::Wrappable {
public: public:
using WrapperType = Bindings::MutationObserverWrapper; static JS::NonnullGCPtr<MutationObserver> create_with_global_object(HTML::Window&, JS::GCPtr<Bindings::CallbackType>);
virtual ~MutationObserver() override;
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;
ExceptionOr<void> observe(Node& target, MutationObserverInit options = {}); ExceptionOr<void> observe(Node& target, MutationObserverInit options = {});
void disconnect(); void disconnect();
@ -56,53 +51,61 @@ public:
} }
private: 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 // 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 // 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; Vector<WeakPtr<Node>> m_node_list;
// https://dom.spec.whatwg.org/#concept-mo-queue // 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 // https://dom.spec.whatwg.org/#registered-observer
struct RegisteredObserver : public RefCounted<RegisteredObserver> { class RegisteredObserver : public JS::Cell {
static NonnullRefPtr<RegisteredObserver> create(MutationObserver& observer, MutationObserverInit& options) JS_CELL(RegisteredObserver, JS::Cell);
{
return adopt_ref(*new RegisteredObserver(observer, options));
}
RegisteredObserver(MutationObserver& observer, MutationObserverInit& options) public:
: observer(observer) static JS::NonnullGCPtr<RegisteredObserver> create(MutationObserver&, MutationObserverInit const&);
, options(options) virtual ~RegisteredObserver() override;
{
}
virtual ~RegisteredObserver() = default; JS::NonnullGCPtr<MutationObserver> observer() const { return m_observer; }
NonnullRefPtr<MutationObserver> observer; MutationObserverInit const& options() const { return m_options; }
MutationObserverInit 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 // https://dom.spec.whatwg.org/#transient-registered-observer
struct TransientRegisteredObserver final : public RegisteredObserver { class TransientRegisteredObserver final : public RegisteredObserver {
static NonnullRefPtr<TransientRegisteredObserver> create(MutationObserver& observer, MutationObserverInit& options, RegisteredObserver& source) JS_CELL(TransientRegisteredObserver, RegisteredObserver);
{
return adopt_ref(*new TransientRegisteredObserver(observer, options, source));
}
TransientRegisteredObserver(MutationObserver& observer, MutationObserverInit& options, RegisteredObserver& source) public:
: RegisteredObserver(observer, options) static JS::NonnullGCPtr<TransientRegisteredObserver> create(MutationObserver&, MutationObserverInit const&, RegisteredObserver& source);
, source(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/IDAllocator.h>
#include <AK/StringBuilder.h> #include <AK/StringBuilder.h>
#include <LibJS/AST.h> #include <LibJS/AST.h>
#include <LibJS/Heap/DeferGC.h>
#include <LibJS/Runtime/FunctionObject.h> #include <LibJS/Runtime/FunctionObject.h>
#include <LibWeb/Bindings/MainThreadVM.h> #include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Bindings/NodePrototype.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_last_child.ptr());
visitor.visit(m_next_sibling.ptr()); visitor.visit(m_next_sibling.ptr());
visitor.visit(m_previous_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 // 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. // 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* inclusive_ancestor = parent; inclusive_ancestor; inclusive_ancestor = inclusive_ancestor->parent()) {
for (auto& registered : inclusive_ancestor->m_registered_observer_list) { for (auto& registered : inclusive_ancestor->m_registered_observer_list) {
if (registered.options.subtree) { if (registered.options().subtree) {
auto transient_observer = TransientRegisteredObserver::create(registered.observer, registered.options, registered); auto transient_observer = TransientRegisteredObserver::create(registered.observer(), registered.options(), registered);
m_registered_observer_list.append(move(transient_observer)); 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 // 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) 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. // 1. Let interestedObservers be an empty map.
// mutationObserver -> mappedOldValue // mutationObserver -> mappedOldValue
OrderedHashMap<NonnullRefPtr<MutationObserver>, String> interested_observers; OrderedHashMap<MutationObserver*, String> interested_observers;
// 2. Let nodes be the inclusive ancestors of target. // 2. Let nodes be the inclusive ancestors of target.
Vector<JS::Handle<Node>> nodes; 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& node : nodes) {
for (auto& registered_observer : node->m_registered_observer_list) { for (auto& registered_observer : node->m_registered_observer_list) {
// 1. Let options be registereds options. // 1. Let options be registereds options.
auto& options = registered_observer.options; auto& options = registered_observer.options();
// 2. If none of the following are true // 2. If none of the following are true
// - node is not target and options["subtree"] is false // - 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::characterData && (!options.character_data.has_value() || !options.character_data.value()))
&& !(type == MutationType::childList && !options.child_list)) { && !(type == MutationType::childList && !options.child_list)) {
// 1. Let mo be registereds observer. // 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. // 2. If interestedObservers[mo] does not exist, then set interestedObservers[mo] to null.
if (!interested_observers.contains(mutation_observer)) if (!interested_observers.contains(mutation_observer))

View file

@ -15,7 +15,6 @@
#include <LibWeb/Bindings/Wrappable.h> #include <LibWeb/Bindings/Wrappable.h>
#include <LibWeb/DOM/EventTarget.h> #include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/DOM/ExceptionOr.h> #include <LibWeb/DOM/ExceptionOr.h>
#include <LibWeb/DOM/MutationObserver.h>
namespace Web::DOM { namespace Web::DOM {
@ -217,8 +216,8 @@ public:
size_t length() const; size_t length() const;
NonnullRefPtrVector<RegisteredObserver>& registered_observers_list() { return m_registered_observer_list; } auto& registered_observers_list() { return m_registered_observer_list; }
NonnullRefPtrVector<RegisteredObserver> const& registered_observers_list() const { 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); } 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 // 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 // "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: private:
void queue_tree_mutation_record(JS::NonnullGCPtr<NodeList> added_nodes, JS::NonnullGCPtr<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);

View file

@ -158,6 +158,7 @@ class ParentNode;
class Position; class Position;
class ProcessingInstruction; class ProcessingInstruction;
class Range; class Range;
class RegisteredObserver;
class ShadowRoot; class ShadowRoot;
class StaticNodeList; class StaticNodeList;
class StaticRange; class StaticRange;
@ -469,7 +470,6 @@ class ImageDataWrapper;
class IntersectionObserverWrapper; class IntersectionObserverWrapper;
class LocationObject; class LocationObject;
class MessageChannelWrapper; class MessageChannelWrapper;
class MutationObserverWrapper;
class OptionConstructor; class OptionConstructor;
class Path2DWrapper; class Path2DWrapper;
class RangePrototype; 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/EventTarget NO_INSTANCE)
libweb_js_wrapper(DOM/HTMLCollection) libweb_js_wrapper(DOM/HTMLCollection)
libweb_js_wrapper(DOM/MutationRecord NO_INSTANCE) 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/NamedNodeMap NO_INSTANCE)
libweb_js_wrapper(DOM/Node NO_INSTANCE) libweb_js_wrapper(DOM/Node NO_INSTANCE)
libweb_js_wrapper(DOM/NodeIterator NO_INSTANCE) libweb_js_wrapper(DOM/NodeIterator NO_INSTANCE)