diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 475eb6c16a4..27208583ad4 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -1066,6 +1066,10 @@ set(SOURCES XHR/XMLHttpRequestUpload.cpp XLink/AttributeNames.cpp XML/XMLDocumentBuilder.cpp + XPath/XPathEvaluator.cpp + XPath/XPathExpression.cpp + XPath/XPathNSResolver.cpp + XPath/XPathResult.cpp ) compile_ipc(Worker/WebWorkerClient.ipc Worker/WebWorkerClientEndpoint.h) diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 5d097c45931..c0c7385e50e 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -1292,3 +1292,12 @@ class TrustedTypePolicyFactory; struct TrustedTypePolicyOptions; } + +namespace Web::XPath { + +class XPathEvaluator; +class XPathExpression; +class XPathNSResolver; +class XPathResult; + +} diff --git a/Libraries/LibWeb/XPath/XPathEvaluator.cpp b/Libraries/LibWeb/XPath/XPathEvaluator.cpp new file mode 100644 index 00000000000..68662d23e83 --- /dev/null +++ b/Libraries/LibWeb/XPath/XPathEvaluator.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025, Johannes Gustafsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +#include "XPathEvaluator.h" +#include "XPathExpression.h" +#include "XPathResult.h" + +namespace Web::XPath { + +GC_DEFINE_ALLOCATOR(XPathEvaluator); + +XPathEvaluator::XPathEvaluator(JS::Realm& realm) + : Web::Bindings::PlatformObject(realm) +{ +} + +XPathEvaluator::~XPathEvaluator() = default; + +WebIDL::ExceptionOr> XPathEvaluator::construct_impl(JS::Realm& realm) +{ + return realm.create(realm); +} + +void XPathEvaluator::initialize(JS::Realm& realm) +{ + WEB_SET_PROTOTYPE_FOR_INTERFACE(XPathEvaluator); + Base::initialize(realm); +} + +WebIDL::ExceptionOr> XPathEvaluator::create_expression(String const& expression, GC::Ptr resolver) +{ + auto& realm = this->realm(); + return realm.create(realm, expression, resolver); +} + +WebIDL::ExceptionOr> XPathEvaluator::evaluate(String const&, DOM::Node const&, GC::Ptr, WebIDL::UnsignedShort, GC::Ptr) +{ + auto& realm = this->realm(); + return realm.create(realm); +} + +GC::Ref XPathEvaluator::create_ns_resolver(GC::Ref node_resolver) +{ + return node_resolver; +} + +} diff --git a/Libraries/LibWeb/XPath/XPathEvaluator.h b/Libraries/LibWeb/XPath/XPathEvaluator.h new file mode 100644 index 00000000000..275f5bc386c --- /dev/null +++ b/Libraries/LibWeb/XPath/XPathEvaluator.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025, Johannes Gustafsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +#include "XPathExpression.h" +#include "XPathNSResolver.h" +#include "XPathResult.h" + +namespace Web::XPath { + +class XPathEvaluator : public Bindings::PlatformObject { + WEB_PLATFORM_OBJECT(XPathEvaluator, Bindings::PlatformObject); + GC_DECLARE_ALLOCATOR(XPathEvaluator); + + explicit XPathEvaluator(JS::Realm&); + virtual ~XPathEvaluator() override; + +public: + static WebIDL::ExceptionOr> construct_impl(JS::Realm&); + virtual void initialize(JS::Realm&) override; + + WebIDL::ExceptionOr> create_expression(String const& expression, GC::Ptr resolver = nullptr); + WebIDL::ExceptionOr> evaluate(String const& expression, DOM::Node const& context_node, GC::Ptr resolver = nullptr, WebIDL::UnsignedShort type = 0, GC::Ptr result = nullptr); + static GC::Ref create_ns_resolver(GC::Ref node_resolver); // legacy +}; + +} diff --git a/Libraries/LibWeb/XPath/XPathEvaluator.idl b/Libraries/LibWeb/XPath/XPathEvaluator.idl new file mode 100644 index 00000000000..bd3dc28ae50 --- /dev/null +++ b/Libraries/LibWeb/XPath/XPathEvaluator.idl @@ -0,0 +1,24 @@ +#import + +// FIXME: callback interfaces are not currently supported +// callback interface XPathNSResolver { +// DOMString? lookupNamespaceURI(DOMString? prefix); +// }; + +// https://dom.spec.whatwg.org/#mixin-xpathevaluatorbase +interface mixin XPathEvaluatorBase { + [NewObject] XPathExpression createExpression(DOMString expression, optional XPathNSResolver? resolver = null); + Node createNSResolver(Node nodeResolver); // legacy + // XPathResult.ANY_TYPE = 0 + XPathResult evaluate(DOMString expression, Node contextNode, optional XPathNSResolver? resolver = null, optional unsigned short type = 0, optional XPathResult? result = null); +}; +Document includes XPathEvaluatorBase; + +// https://dom.spec.whatwg.org/#interface-xpathevaluator +[Exposed=Window] +interface XPathEvaluator { + constructor(); +}; + +XPathEvaluator includes XPathEvaluatorBase; + diff --git a/Libraries/LibWeb/XPath/XPathExpression.cpp b/Libraries/LibWeb/XPath/XPathExpression.cpp new file mode 100644 index 00000000000..fdca5588296 --- /dev/null +++ b/Libraries/LibWeb/XPath/XPathExpression.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025, Johannes Gustafsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +#include "XPathEvaluator.h" +#include "XPathExpression.h" +#include "XPathResult.h" + +namespace Web::XPath { + +GC_DEFINE_ALLOCATOR(XPathExpression); + +XPathExpression::XPathExpression(JS::Realm& realm, String const& expression, GC::Ptr resolver) + : Web::Bindings::PlatformObject(realm) + , m_expression(expression) + , m_resolver(resolver) +{ +} + +void XPathExpression::initialize(JS::Realm& realm) +{ + WEB_SET_PROTOTYPE_FOR_INTERFACE(XPathExpression); + Base::initialize(realm); +} + +void XPathExpression::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_resolver); +} + +XPathExpression::~XPathExpression() = default; + +WebIDL::ExceptionOr> XPathExpression::evaluate(DOM::Node const&, WebIDL::UnsignedShort, GC::Ptr) +{ + auto& realm = this->realm(); + return realm.create(realm); +} + +} diff --git a/Libraries/LibWeb/XPath/XPathExpression.h b/Libraries/LibWeb/XPath/XPathExpression.h new file mode 100644 index 00000000000..ba4de0439a7 --- /dev/null +++ b/Libraries/LibWeb/XPath/XPathExpression.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025, Johannes Gustafsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +#include "XPathResult.h" + +namespace Web::XPath { + +class XPathExpression final : public Bindings::PlatformObject { + WEB_PLATFORM_OBJECT(XPathExpression, Bindings::PlatformObject); + GC_DECLARE_ALLOCATOR(XPathExpression); + +public: + explicit XPathExpression(JS::Realm&, String const& expression, GC::Ptr resolver); + virtual ~XPathExpression() override; + virtual void visit_edges(Cell::Visitor&) override; + virtual void initialize(JS::Realm&) override; + + WebIDL::ExceptionOr> evaluate(DOM::Node const& context_node, WebIDL::UnsignedShort type = 0, GC::Ptr result = nullptr); + +private: + String m_expression; + GC::Ptr m_resolver; +}; + +} diff --git a/Libraries/LibWeb/XPath/XPathExpression.idl b/Libraries/LibWeb/XPath/XPathExpression.idl new file mode 100644 index 00000000000..c039be0ed1b --- /dev/null +++ b/Libraries/LibWeb/XPath/XPathExpression.idl @@ -0,0 +1,9 @@ +#import + +// https://dom.spec.whatwg.org/#interface-xpathexpression +[Exposed=Window] +interface XPathExpression { + // XPathResult.ANY_TYPE = 0 + XPathResult evaluate(Node contextNode, optional unsigned short type = 0, optional XPathResult? result = null); +}; + diff --git a/Libraries/LibWeb/XPath/XPathNSResolver.cpp b/Libraries/LibWeb/XPath/XPathNSResolver.cpp new file mode 100644 index 00000000000..602508713dc --- /dev/null +++ b/Libraries/LibWeb/XPath/XPathNSResolver.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025, Johannes Gustafsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +#include "XPathNSResolver.h" + +namespace Web::XPath { + +GC_DEFINE_ALLOCATOR(XPathNSResolver); + +GC::Ref XPathNSResolver::create(JS::Realm& realm, GC::Ref callback) +{ + return realm.create(realm, callback); +} + +XPathNSResolver::XPathNSResolver(JS::Realm& realm, GC::Ref callback) + : JS::Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().object_prototype()) + , m_callback(callback) +{ +} + +void XPathNSResolver::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_callback); +} + +} diff --git a/Libraries/LibWeb/XPath/XPathNSResolver.h b/Libraries/LibWeb/XPath/XPathNSResolver.h new file mode 100644 index 00000000000..3addbaf52c4 --- /dev/null +++ b/Libraries/LibWeb/XPath/XPathNSResolver.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025, Johannes Gustafsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +#include "XPathResult.h" + +namespace Web::XPath { + +class XPathNSResolver final : public JS::Object { + JS_OBJECT(XPathNSResolver, JS::Object); + GC_DECLARE_ALLOCATOR(XPathNSResolver); + +public: + [[nodiscard]] static GC::Ref create(JS::Realm&, GC::Ref); + XPathNSResolver(JS::Realm&, GC::Ref); + + virtual ~XPathNSResolver() = default; + virtual void visit_edges(Cell::Visitor&) override; + +private: + GC::Ref m_callback; +}; + +} diff --git a/Libraries/LibWeb/XPath/XPathResult.cpp b/Libraries/LibWeb/XPath/XPathResult.cpp new file mode 100644 index 00000000000..b6a023ca5d3 --- /dev/null +++ b/Libraries/LibWeb/XPath/XPathResult.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Johannes Gustafsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +#include "XPathResult.h" + +namespace Web::XPath { + +GC_DEFINE_ALLOCATOR(XPathResult); + +XPathResult::XPathResult(JS::Realm& realm) + : Web::Bindings::PlatformObject(realm) +{ + m_node_set_iter = m_node_set.end(); +} + +void XPathResult::initialize(JS::Realm& realm) +{ + WEB_SET_PROTOTYPE_FOR_INTERFACE(XPathResult); + Base::initialize(realm); +} + +void XPathResult::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_node_set); +} + +XPathResult::~XPathResult() = default; + +GC::Ptr XPathResult::iterate_next() +{ + if (m_node_set_iter == m_node_set.end()) + return nullptr; + + return *m_node_set_iter++; +} + +GC::Ptr XPathResult::snapshot_item(int index) +{ + if (index < 0 || static_cast(index) >= m_node_set.size()) + return nullptr; + + return m_node_set.at(index); +} + +} diff --git a/Libraries/LibWeb/XPath/XPathResult.h b/Libraries/LibWeb/XPath/XPathResult.h new file mode 100644 index 00000000000..4b314192116 --- /dev/null +++ b/Libraries/LibWeb/XPath/XPathResult.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025, Johannes Gustafsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::XPath { + +class XPathResult : public Bindings::PlatformObject { + WEB_PLATFORM_OBJECT(XPathResult, Bindings::PlatformObject); + GC_DECLARE_ALLOCATOR(XPathResult); + +public: + static WebIDL::UnsignedShort const ANY_TYPE = 0; + static WebIDL::UnsignedShort const NUMBER_TYPE = 1; + static WebIDL::UnsignedShort const STRING_TYPE = 2; + static WebIDL::UnsignedShort const BOOLEAN_TYPE = 3; + static WebIDL::UnsignedShort const UNORDERED_NODE_ITERATOR_TYPE = 4; + static WebIDL::UnsignedShort const ORDERED_NODE_ITERATOR_TYPE = 5; + static WebIDL::UnsignedShort const UNORDERED_NODE_SNAPSHOT_TYPE = 6; + static WebIDL::UnsignedShort const ORDERED_NODE_SNAPSHOT_TYPE = 7; + static WebIDL::UnsignedShort const ANY_UNORDERED_NODE_TYPE = 8; + static WebIDL::UnsignedShort const FIRST_ORDERED_NODE_TYPE = 9; + + XPathResult(JS::Realm&); + virtual ~XPathResult() override; + virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Cell::Visitor&) override; + + WebIDL::UnsignedShort result_type() const { return m_result_type; } + WebIDL::Double number_value() const { return m_number_value; } + String string_value() { return m_string_value; } + WebIDL::Boolean boolean_value() const { return m_boolean_value; } + GC::Ptr single_node_value() { return m_node_set.is_empty() ? nullptr : m_node_set.first(); } + WebIDL::Boolean invalid_iterator_state() const { return m_invalid_iterator_state; } + WebIDL::UnsignedLong snapshot_length() const { return m_node_set.size(); } + + GC::Ptr iterate_next(); + GC::Ptr snapshot_item(int index); + +private: + WebIDL::UnsignedShort m_result_type; + WebIDL::Double m_number_value; + String m_string_value; + WebIDL::Boolean m_boolean_value; + WebIDL::Boolean m_invalid_iterator_state; + WebIDL::UnsignedLong m_snapshot_length; + + Vector> m_node_set; + Vector>::Iterator m_node_set_iter; +}; + +} diff --git a/Libraries/LibWeb/XPath/XPathResult.idl b/Libraries/LibWeb/XPath/XPathResult.idl new file mode 100644 index 00000000000..6344fec60da --- /dev/null +++ b/Libraries/LibWeb/XPath/XPathResult.idl @@ -0,0 +1,29 @@ +#import + +// https://dom.spec.whatwg.org/#interface-xpathresult +[Exposed=Window] +interface XPathResult { + const unsigned short ANY_TYPE = 0; + const unsigned short NUMBER_TYPE = 1; + const unsigned short STRING_TYPE = 2; + const unsigned short BOOLEAN_TYPE = 3; + const unsigned short UNORDERED_NODE_ITERATOR_TYPE = 4; + const unsigned short ORDERED_NODE_ITERATOR_TYPE = 5; + const unsigned short UNORDERED_NODE_SNAPSHOT_TYPE = 6; + const unsigned short ORDERED_NODE_SNAPSHOT_TYPE = 7; + const unsigned short ANY_UNORDERED_NODE_TYPE = 8; + const unsigned short FIRST_ORDERED_NODE_TYPE = 9; + + readonly attribute unsigned short resultType; + readonly attribute unrestricted double numberValue; + readonly attribute DOMString stringValue; + readonly attribute boolean booleanValue; + readonly attribute Node? singleNodeValue; + readonly attribute boolean invalidIteratorState; + readonly attribute unsigned long snapshotLength; + + Node? iterateNext(); + Node? snapshotItem(unsigned long index); +}; + + diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index 743aae80765..d7bca073328 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -502,3 +502,6 @@ libweb_js_bindings(XHR/ProgressEvent) libweb_js_bindings(XHR/XMLHttpRequest) libweb_js_bindings(XHR/XMLHttpRequestEventTarget) libweb_js_bindings(XHR/XMLHttpRequestUpload) +libweb_js_bindings(XPath/XPathResult) +libweb_js_bindings(XPath/XPathExpression) +libweb_js_bindings(XPath/XPathEvaluator) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index 6128ed53e03..4119454401f 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -160,6 +160,7 @@ static bool is_platform_object(Type const& type) "Window"sv, "WindowProxy"sv, "WritableStream"sv, + "XPathResult"sv, }; if (type.name().ends_with("Element"sv)) return true; @@ -681,7 +682,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter generate_to_string(scoped_generator, parameter, variadic, optional, optional_default_value); } else if (parameter.type->is_boolean() || parameter.type->is_integer()) { generate_to_integral(scoped_generator, parameter, optional, optional_default_value); - } else if (parameter.type->name().is_one_of("EventListener", "NodeFilter")) { + } else if (parameter.type->name().is_one_of("EventListener", "NodeFilter", "XPathNSResolver")) { // FIXME: Replace this with support for callback interfaces. https://webidl.spec.whatwg.org/#idl-callback-interface if (parameter.type->name() == "EventListener") @@ -4973,6 +4974,7 @@ using namespace Web::WebGL::Extensions; using namespace Web::WebIDL; using namespace Web::WebVTT; using namespace Web::XHR; +using namespace Web::XPath; )~~~"sv); } diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/Namespaces.h b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/Namespaces.h index e026e6b0307..4c4e09de665 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/Namespaces.h +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/Namespaces.h @@ -49,6 +49,7 @@ static constexpr Array libweb_interface_namespaces = { "WebIDL"sv, "WebSockets"sv, "XHR"sv, + "XPath"sv, }; } diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index 664ae75e963..0422a9f95b5 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -525,6 +525,9 @@ XMLHttpRequest XMLHttpRequestEventTarget XMLHttpRequestUpload XMLSerializer +XPathEvaluator +XPathExpression +XPathResult __finishTest __preventMultipleTestFunctions animationFrame