ladybird/Userland/Libraries/LibWeb/DOM/ParentNode.cpp
Luke Wilde d47e431d54 LibWeb: Add getElementsByTagNameNS and add support for * in non-NS
This also moves getElementsByTagName to ParentNode to remove the code
duplication between Document and Element. This additionally fixes a bug
where getElementsByTagName did not check if the element was a
descendant, meaning it would also include the context element if the
condition matched.
2021-09-22 20:33:06 +02:00

155 lines
6.2 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2020, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/SelectorEngine.h>
#include <LibWeb/DOM/HTMLCollection.h>
#include <LibWeb/DOM/ParentNode.h>
#include <LibWeb/Dump.h>
#include <LibWeb/Namespace.h>
namespace Web::DOM {
ExceptionOr<RefPtr<Element>> ParentNode::query_selector(StringView selector_text)
{
auto maybe_selectors = parse_selector(CSS::ParsingContext(*this), selector_text);
if (!maybe_selectors.has_value())
return DOM::SyntaxError::create("Failed to parse selector");
auto selectors = maybe_selectors.value();
RefPtr<Element> result;
for_each_in_inclusive_subtree_of_type<Element>([&](auto& element) {
for (auto& selector : selectors) {
if (SelectorEngine::matches(selector, element)) {
result = element;
return IterationDecision::Break;
}
}
return IterationDecision::Continue;
});
return result;
}
ExceptionOr<NonnullRefPtrVector<Element>> ParentNode::query_selector_all(StringView selector_text)
{
auto maybe_selectors = parse_selector(CSS::ParsingContext(*this), selector_text);
if (!maybe_selectors.has_value())
return DOM::SyntaxError::create("Failed to parse selector");
auto selectors = maybe_selectors.value();
NonnullRefPtrVector<Element> elements;
for_each_in_inclusive_subtree_of_type<Element>([&](auto& element) {
for (auto& selector : selectors) {
if (SelectorEngine::matches(selector, element)) {
elements.append(element);
}
}
return IterationDecision::Continue;
});
return elements;
}
RefPtr<Element> ParentNode::first_element_child()
{
return first_child_of_type<Element>();
}
RefPtr<Element> ParentNode::last_element_child()
{
return last_child_of_type<Element>();
}
// https://dom.spec.whatwg.org/#dom-parentnode-childelementcount
u32 ParentNode::child_element_count() const
{
u32 count = 0;
for (auto* child = first_child(); child; child = child->next_sibling()) {
if (is<Element>(child))
++count;
}
return count;
}
// https://dom.spec.whatwg.org/#dom-parentnode-children
NonnullRefPtr<HTMLCollection> ParentNode::children()
{
// The children getter steps are to return an HTMLCollection collection rooted at this matching only element children.
// FIXME: This should return the same HTMLCollection object every time,
// but that would cause a reference cycle since HTMLCollection refs the root.
return HTMLCollection::create(*this, [this](Element const& element) {
return is_parent_of(element);
});
}
// https://dom.spec.whatwg.org/#concept-getelementsbytagname
// NOTE: This method is only exposed on Document and Element, but is in ParentNode to prevent code duplication.
NonnullRefPtr<HTMLCollection> ParentNode::get_elements_by_tag_name(FlyString const& qualified_name)
{
// 1. If qualifiedName is "*" (U+002A), return a HTMLCollection rooted at root, whose filter matches only descendant elements.
if (qualified_name == "*") {
return HTMLCollection::create(*this, [this](Element const& element) {
return element.is_descendant_of(*this);
});
}
// FIXME: 2. Otherwise, if roots node document is an HTML document, return a HTMLCollection rooted at root, whose filter matches the following descendant elements:
// (It is currently always a HTML document)
return HTMLCollection::create(*this, [this, qualified_name](Element const& element) {
if (!element.is_descendant_of(*this))
return false;
// - Whose namespace is the HTML namespace and whose qualified name is qualifiedName, in ASCII lowercase.
if (element.namespace_() == Namespace::HTML)
return element.qualified_name().to_lowercase() == qualified_name.to_lowercase();
// - Whose namespace is not the HTML namespace and whose qualified name is qualifiedName.
return element.qualified_name() == qualified_name;
});
// FIXME: 3. Otherwise, return a HTMLCollection rooted at root, whose filter matches descendant elements whose qualified name is qualifiedName.
}
// https://dom.spec.whatwg.org/#concept-getelementsbytagnamens
// NOTE: This method is only exposed on Document and Element, but is in ParentNode to prevent code duplication.
NonnullRefPtr<HTMLCollection> ParentNode::get_elements_by_tag_name_ns(FlyString const& nullable_namespace, FlyString const& local_name)
{
// 1. If namespace is the empty string, set it to null.
String namespace_ = nullable_namespace;
if (namespace_.is_empty())
namespace_ = {};
// 2. If both namespace and localName are "*" (U+002A), return a HTMLCollection rooted at root, whose filter matches descendant elements.
if (namespace_ == "*" && local_name == "*") {
return HTMLCollection::create(*this, [this](Element const& element) {
return element.is_descendant_of(*this);
});
}
// 3. Otherwise, if namespace is "*" (U+002A), return a HTMLCollection rooted at root, whose filter matches descendant elements whose local name is localName.
if (namespace_ == "*") {
return HTMLCollection::create(*this, [this, local_name](Element const& element) {
return element.is_descendant_of(*this) && element.local_name() == local_name;
});
}
// 4. Otherwise, if localName is "*" (U+002A), return a HTMLCollection rooted at root, whose filter matches descendant elements whose namespace is namespace.
if (local_name == "*") {
return HTMLCollection::create(*this, [this, namespace_](Element const& element) {
return element.is_descendant_of(*this) && element.namespace_() == namespace_;
});
}
// 5. Otherwise, return a HTMLCollection rooted at root, whose filter matches descendant elements whose namespace is namespace and local name is localName.
return HTMLCollection::create(*this, [this, namespace_, local_name](Element const& element) {
return element.is_descendant_of(*this) && element.namespace_() == namespace_ && element.local_name() == local_name;
});
}
}