diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator/IDLGenerators.cpp index 2d979e28c6..f5372124bb 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator/IDLGenerators.cpp @@ -320,18 +320,23 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter )~~~"); } } - } else if (parameter.type->name == "EventListener") { + } else if (parameter.type->name.is_one_of("EventListener", "NodeFilter")) { // FIXME: Replace this with support for callback interfaces. https://heycam.github.io/webidl/#idl-callback-interface + if (parameter.type->name == "EventListener") + scoped_generator.set("cpp_type", "IDLEventListener"); + else + scoped_generator.set("cpp_type", parameter.type->name); + if (parameter.type->nullable) { scoped_generator.append(R"~~~( - RefPtr @cpp_name@; + RefPtr<@cpp_type@> @cpp_name@; if (!@js_name@@js_suffix@.is_nullish()) { if (!@js_name@@js_suffix@.is_object()) return vm.throw_completion(global_object, JS::ErrorType::NotAnObject, @js_name@@js_suffix@.to_string_without_side_effects()); CallbackType callback_type(JS::make_handle(&@js_name@@js_suffix@.as_object()), HTML::incumbent_settings_object()); - @cpp_name@ = adopt_ref(*new IDLEventListener(move(callback_type))); + @cpp_name@ = adopt_ref(*new @cpp_type@(move(callback_type))); } )~~~"); } else { @@ -340,7 +345,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter return vm.throw_completion(global_object, JS::ErrorType::NotAnObject, @js_name@@js_suffix@.to_string_without_side_effects()); CallbackType callback_type(JS::make_handle(&@js_name@@js_suffix@.as_object()), HTML::incumbent_settings_object()); - auto @cpp_name@ = adopt_ref(*new IDLEventListener(move(callback_type))); + auto @cpp_name@ = adopt_ref(*new @cpp_type@(move(callback_type))); )~~~"); } } else if (IDL::is_wrappable_type(*parameter.type)) { @@ -2803,6 +2808,7 @@ void generate_prototype_implementation(IDL::Interface const& interface) #include #include #include +#include #include #include #include diff --git a/Userland/Libraries/LibWeb/Bindings/WindowObjectHelper.h b/Userland/Libraries/LibWeb/Bindings/WindowObjectHelper.h index f30f345391..1c0d12339d 100644 --- a/Userland/Libraries/LibWeb/Bindings/WindowObjectHelper.h +++ b/Userland/Libraries/LibWeb/Bindings/WindowObjectHelper.h @@ -233,6 +233,8 @@ #include #include #include +#include +#include #include #include #include @@ -439,6 +441,7 @@ ADD_WINDOW_OBJECT_INTERFACE(MessageEvent) \ ADD_WINDOW_OBJECT_INTERFACE(MouseEvent) \ ADD_WINDOW_OBJECT_INTERFACE(Node) \ + ADD_WINDOW_OBJECT_INTERFACE(NodeIterator) \ ADD_WINDOW_OBJECT_INTERFACE(NodeList) \ ADD_WINDOW_OBJECT_INTERFACE(PageTransitionEvent) \ ADD_WINDOW_OBJECT_INTERFACE(Performance) \ diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 1b16cac009..eef6d1fbc7 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -88,6 +88,7 @@ set(SOURCES DOM/LiveNodeList.cpp DOM/NamedNodeMap.cpp DOM/Node.cpp + DOM/NodeIterator.cpp DOM/NodeOperations.cpp DOM/ParentNode.cpp DOM/Position.cpp @@ -436,6 +437,7 @@ libweb_js_wrapper(DOM/EventTarget) libweb_js_wrapper(DOM/HTMLCollection) libweb_js_wrapper(DOM/NamedNodeMap) libweb_js_wrapper(DOM/Node) +libweb_js_wrapper(DOM/NodeIterator) libweb_js_wrapper(DOM/NodeList) libweb_js_wrapper(DOM/ProcessingInstruction) libweb_js_wrapper(DOM/Range) diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index af4be273d3..a1d8df1fad 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2021, Andreas Kling + * Copyright (c) 2018-2022, Andreas Kling * Copyright (c) 2021, Linus Groh * Copyright (c) 2021, Luke Wilde * Copyright (c) 2021, Sam Atkins @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -1460,4 +1461,10 @@ ExceptionOr Document::validate_qualified_name(String }; } +// https://dom.spec.whatwg.org/#dom-document-createnodeiterator +NonnullRefPtr Document::create_node_iterator(Node& root, unsigned what_to_show, RefPtr filter) +{ + return NodeIterator::create(root, what_to_show, move(filter)); +} + } diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h index 6da28d8a62..299ce9d4b1 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.h +++ b/Userland/Libraries/LibWeb/DOM/Document.h @@ -324,6 +324,8 @@ public: }; static ExceptionOr validate_qualified_name(String const& qualified_name); + NonnullRefPtr create_node_iterator(Node& root, unsigned what_to_show, RefPtr); + private: explicit Document(const AK::URL&); diff --git a/Userland/Libraries/LibWeb/DOM/Document.idl b/Userland/Libraries/LibWeb/DOM/Document.idl index 50b1baceef..e7b9401e85 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.idl +++ b/Userland/Libraries/LibWeb/DOM/Document.idl @@ -7,6 +7,8 @@ #import #import #import +#import +#import #import #import #import @@ -173,4 +175,6 @@ interface Document : Node { readonly boolean hidden; readonly DOMString visibilityState; + [NewObject] NodeIterator createNodeIterator(Node root, optional unsigned long whatToShow = 0xFFFFFFFF, optional NodeFilter? filter = null); + }; diff --git a/Userland/Libraries/LibWeb/DOM/NodeFilter.h b/Userland/Libraries/LibWeb/DOM/NodeFilter.h new file mode 100644 index 0000000000..47f530e99e --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/NodeFilter.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Web::DOM { + +class NodeFilter + : public RefCounted + , public Bindings::Wrappable { +public: + using WrapperType = Bindings::NodeFilterWrapper; + + explicit NodeFilter(Bindings::CallbackType callback) + : m_callback(move(callback)) + { + } + + virtual ~NodeFilter() = default; + + Bindings::CallbackType& callback() { return m_callback; } + + enum Result { + FILTER_ACCEPT = 1, + FILTER_REJECT = 2, + FILTER_SKIP = 3, + }; + +private: + Bindings::CallbackType m_callback; +}; + +inline JS::Object* wrap(JS::GlobalObject&, Web::DOM::NodeFilter& filter) +{ + return filter.callback().callback.cell(); +} + +} diff --git a/Userland/Libraries/LibWeb/DOM/NodeFilter.idl b/Userland/Libraries/LibWeb/DOM/NodeFilter.idl new file mode 100644 index 0000000000..94215112f9 --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/NodeFilter.idl @@ -0,0 +1,30 @@ +#import + +[Exposed=Window] +interface NodeFilter { + + // FIXME: This should be a callback interface. + + // Constants for acceptNode() + const unsigned short FILTER_ACCEPT = 1; + const unsigned short FILTER_REJECT = 2; + const unsigned short FILTER_SKIP = 3; + + // Constants for whatToShow + const unsigned long SHOW_ALL = 0xFFFFFFFF; + const unsigned long SHOW_ELEMENT = 0x1; + const unsigned long SHOW_ATTRIBUTE = 0x2; + const unsigned long SHOW_TEXT = 0x4; + const unsigned long SHOW_CDATA_SECTION = 0x8; + const unsigned long SHOW_ENTITY_REFERENCE = 0x10; // legacy + const unsigned long SHOW_ENTITY = 0x20; // legacy + const unsigned long SHOW_PROCESSING_INSTRUCTION = 0x40; + const unsigned long SHOW_COMMENT = 0x80; + const unsigned long SHOW_DOCUMENT = 0x100; + const unsigned long SHOW_DOCUMENT_TYPE = 0x200; + const unsigned long SHOW_DOCUMENT_FRAGMENT = 0x400; + const unsigned long SHOW_NOTATION = 0x800; // legacy + + unsigned short acceptNode(Node node); + +}; diff --git a/Userland/Libraries/LibWeb/DOM/NodeIterator.cpp b/Userland/Libraries/LibWeb/DOM/NodeIterator.cpp new file mode 100644 index 0000000000..0abba71695 --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/NodeIterator.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2022, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace Web::DOM { + +NodeIterator::NodeIterator(Node& root) + : m_root(root) + , m_reference(root) +{ +} + +// https://dom.spec.whatwg.org/#dom-document-createnodeiterator +NonnullRefPtr NodeIterator::create(Node& root, unsigned what_to_show, RefPtr filter) +{ + // 1. Let iterator be a new NodeIterator object. + // 2. Set iterator’s root and iterator’s reference to root. + auto iterator = adopt_ref(*new NodeIterator(root)); + + // 3. Set iterator’s pointer before reference to true. + iterator->m_pointer_before_reference = true; + + // 4. Set iterator’s whatToShow to whatToShow. + iterator->m_what_to_show = what_to_show; + + // 5. Set iterator’s filter to filter. + iterator->m_filter = move(filter); + + // 6. Return iterator. + return iterator; +} + +// https://dom.spec.whatwg.org/#dom-nodeiterator-detach +void NodeIterator::detach() +{ + // The detach() method steps are to do nothing. + // Its functionality (disabling a NodeIterator object) was removed, but the method itself is preserved for compatibility. +} + +// https://dom.spec.whatwg.org/#concept-nodeiterator-traverse +JS::ThrowCompletionOr> NodeIterator::traverse(Direction direction) +{ + // 1. Let node be iterator’s reference. + auto node = m_reference; + + // 2. Let beforeNode be iterator’s pointer before reference. + auto before_node = m_pointer_before_reference; + + // 3. While true: + while (true) { + // 4. Branch on direction: + if (direction == Direction::Next) { + // next + // If beforeNode is false, then set node to the first node following node in iterator’s iterator collection. + // If there is no such node, then return null. + if (!before_node) { + auto* next_node = node->next_in_pre_order(m_root.ptr()); + if (!next_node) + return RefPtr {}; + node = *next_node; + } else { + // If beforeNode is true, then set it to false. + before_node = false; + } + } else { + // previous + // If beforeNode is true, then set node to the first node preceding node in iterator’s iterator collection. + // If there is no such node, then return null. + if (before_node) { + if (node == m_root.ptr()) + return nullptr; + auto* previous_node = node->previous_in_pre_order(); + if (!previous_node) + return RefPtr {}; + node = *previous_node; + } else { + // If beforeNode is false, then set it to true. + before_node = true; + } + } + + // 2. Let result be the result of filtering node within iterator. + auto result = filter(*node); + if (result.is_throw_completion()) + return result.release_error(); + + // 3. If result is FILTER_ACCEPT, then break. + if (result.value() == NodeFilter::FILTER_ACCEPT) + break; + } + + // 4. Set iterator’s reference to node. + m_reference = node; + + // 5. Set iterator’s pointer before reference to beforeNode. + m_pointer_before_reference = before_node; + + // 6. Return node. + return RefPtr { node }; +} + +// https://dom.spec.whatwg.org/#concept-node-filter +JS::ThrowCompletionOr NodeIterator::filter(Node& node) +{ + VERIFY(wrapper()); + auto& global_object = wrapper()->global_object(); + + // 1. If traverser’s active flag is set, then throw an "InvalidStateError" DOMException. + if (m_active) + return JS::throw_completion(wrap(global_object, InvalidStateError::create("NodeIterator is already active"))); + + // 2. Let n be node’s nodeType attribute value − 1. + auto n = node.node_type() - 1; + + // 3. If the nth bit (where 0 is the least significant bit) of traverser’s whatToShow is not set, then return FILTER_SKIP. + if (!(m_what_to_show & (1u << n))) + return NodeFilter::FILTER_SKIP; + + // 4. If traverser’s filter is null, then return FILTER_ACCEPT. + if (!m_filter) + return NodeFilter::FILTER_ACCEPT; + + // 5. Set traverser’s active flag. + m_active = true; + + // 6. Let result be the return value of call a user object’s operation with traverser’s filter, "acceptNode", and « node ». + // If this throws an exception, then unset traverser’s active flag and rethrow the exception. + auto result = Bindings::IDL::call_user_object_operation(m_filter->callback(), "acceptNode", {}, wrap(global_object, node)); + if (result.is_abrupt()) { + m_active = false; + return result; + } + + // 7. Unset traverser’s active flag. + m_active = false; + + // 8. Return result. + auto result_value = TRY(result.value()->to_i32(global_object)); + return static_cast(result_value); +} + +// https://dom.spec.whatwg.org/#dom-nodeiterator-nextnode +JS::ThrowCompletionOr> NodeIterator::next_node() +{ + return traverse(Direction::Next); +} + +// https://dom.spec.whatwg.org/#dom-nodeiterator-previousnode +JS::ThrowCompletionOr> NodeIterator::previous_node() +{ + return traverse(Direction::Previous); +} + +} diff --git a/Userland/Libraries/LibWeb/DOM/NodeIterator.h b/Userland/Libraries/LibWeb/DOM/NodeIterator.h new file mode 100644 index 0000000000..4df146617c --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/NodeIterator.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Web::DOM { + +// https://dom.spec.whatwg.org/#nodeiterator +class NodeIterator + : public RefCounted + , public Bindings::Wrappable { +public: + using WrapperType = Bindings::NodeIteratorWrapper; + + static NonnullRefPtr create(Node& root, unsigned what_to_show, RefPtr); + + NonnullRefPtr root() { return m_root; } + NonnullRefPtr reference_node() { return m_reference; } + bool pointer_before_reference_node() const { return m_pointer_before_reference; } + unsigned what_to_show() const { return m_what_to_show; } + + NodeFilter* filter() { return m_filter; } + + JS::ThrowCompletionOr> next_node(); + JS::ThrowCompletionOr> previous_node(); + + void detach(); + +private: + NodeIterator(Node& root); + + enum class Direction { + Next, + Previous, + }; + + JS::ThrowCompletionOr> traverse(Direction); + + JS::ThrowCompletionOr filter(Node&); + + // https://dom.spec.whatwg.org/#concept-traversal-root + NonnullRefPtr m_root; + + // https://dom.spec.whatwg.org/#nodeiterator-reference + NonnullRefPtr m_reference; + + // https://dom.spec.whatwg.org/#nodeiterator-pointer-before-reference + bool m_pointer_before_reference { true }; + + // https://dom.spec.whatwg.org/#concept-traversal-whattoshow + unsigned m_what_to_show { 0 }; + + // https://dom.spec.whatwg.org/#concept-traversal-filter + RefPtr m_filter; + + // https://dom.spec.whatwg.org/#concept-traversal-active + bool m_active { false }; +}; + +} diff --git a/Userland/Libraries/LibWeb/DOM/NodeIterator.idl b/Userland/Libraries/LibWeb/DOM/NodeIterator.idl new file mode 100644 index 0000000000..b46a41ff45 --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/NodeIterator.idl @@ -0,0 +1,18 @@ +#import +#import + +[Exposed=Window] +interface NodeIterator { + + [SameObject] readonly attribute Node root; + readonly attribute Node referenceNode; + readonly attribute boolean pointerBeforeReferenceNode; + readonly attribute unsigned long whatToShow; + readonly attribute NodeFilter? filter; + + Node? nextNode(); + Node? previousNode(); + + undefined detach(); + +}; diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 5ce0491446..f64a3ad00a 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -110,6 +110,8 @@ class IDLEventListener; class LiveNodeList; class NamedNodeMap; class Node; +class NodeFilter; +class NodeIterator; class NodeList; class ParentNode; class Position; @@ -469,6 +471,8 @@ class MessageEventWrapper; class MessagePortWrapper; class MouseEventWrapper; class NamedNodeMapWrapper; +class NodeFilterWrapper; +class NodeIteratorWrapper; class NodeListWrapper; class NodeWrapper; class PageTransitionEventWrapper;