LibWeb: Add naive support for document.querySelectorAll()

This currently returns a JS::Array of elements matching a selector.
The more correct behavior would be to return a static NodeList, but as
we don't have NodeLists right now, that'll be a task for the future.
This commit is contained in:
Andreas Kling 2020-03-29 22:24:23 +02:00
parent c56acba75e
commit 0f7bcd4111
Notes: sideshowbarker 2024-07-19 08:03:42 +09:00
11 changed files with 182 additions and 78 deletions

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div id="foo1" class="foo"></div>
<div id="foo2" class="foo"></div>
<div id="foo3" class="foo"></div>
<pre id="out"></pre>
<script>
var elements = document.querySelectorAll(".foo");
try {
if (elements.length !== 3)
throw 1;
if (elements[0].id !== "foo1")
throw 2;
if (elements[1].id !== "foo2")
throw 3;
if (elements[2].id !== "foo3")
throw 4;
document.getElementById('out').innerHTML = "Success!";
} catch (e) {
document.getElementById('out').innerHTML = "Error number " + e;
}
</script>
</body>
</html>

View File

@ -23,6 +23,7 @@ h1 {
<p>This is a very simple browser built on the LibWeb engine.</p>
<p>Some small test pages:</p>
<ul>
<li><a href="qsa.html">querySelectorAll test</a></li>
<li><a href="innerHTML.html">innerHTML property test</a></li>
<li><a href="position-absolute-top-left.html">position: absolute; for top and left</a></li>
<li><a href="demo.html">fun demo</a></li>

View File

@ -26,6 +26,8 @@
#include <AK/FlyString.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/Value.h>
#include <LibWeb/Bindings/DocumentWrapper.h>
@ -39,6 +41,7 @@ DocumentWrapper::DocumentWrapper(Document& document)
: NodeWrapper(document)
{
put_native_function("getElementById", get_element_by_id);
put_native_function("querySelectorAll", query_selector_all);
}
DocumentWrapper::~DocumentWrapper()
@ -55,22 +58,50 @@ const Document& DocumentWrapper::node() const
return static_cast<const Document&>(NodeWrapper::node());
}
JS::Value DocumentWrapper::get_element_by_id(JS::Interpreter& interpreter)
static Document* document_from(JS::Interpreter& interpreter)
{
auto* this_object = interpreter.this_value().to_object(interpreter.heap());
if (!this_object)
return {};
// FIXME: Verify that it's a DocumentWrapper somehow!
auto& node = static_cast<DocumentWrapper*>(this_object)->node();
if (StringView("DocumentWrapper") != this_object->class_name()) {
interpreter.throw_exception<JS::Error>("TypeError", "That's not a DocumentWrapper, bro.");
return {};
}
return &static_cast<DocumentWrapper*>(this_object)->node();
}
JS::Value DocumentWrapper::get_element_by_id(JS::Interpreter& interpreter)
{
auto* document = document_from(interpreter);
if (!document)
return {};
auto& arguments = interpreter.call_frame().arguments;
if (arguments.is_empty())
return JS::js_null();
auto id = arguments[0].to_string();
auto* element = node.get_element_by_id(id);
auto* element = document->get_element_by_id(id);
if (!element)
return JS::js_null();
return wrap(interpreter.heap(), const_cast<Element&>(*element));
}
JS::Value DocumentWrapper::query_selector_all(JS::Interpreter& interpreter)
{
auto* document = document_from(interpreter);
if (!document)
return {};
auto& arguments = interpreter.call_frame().arguments;
if (arguments.is_empty())
return JS::js_null();
auto selector = arguments[0].to_string();
auto elements = document->query_selector_all(selector);
// FIXME: This should be a static NodeList, not a plain JS::Array.
auto* node_list = interpreter.heap().allocate<JS::Array>();
for (auto& element : elements) {
node_list->push(wrap(interpreter.heap(), element));
}
return node_list;
}
}
}

View File

@ -43,6 +43,7 @@ private:
virtual const char* class_name() const override { return "DocumentWrapper"; }
static JS::Value get_element_by_id(JS::Interpreter&);
static JS::Value query_selector_all(JS::Interpreter&);
};
}

View File

@ -34,6 +34,7 @@
#include <LibJS/Runtime/Function.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibWeb/Bindings/DocumentWrapper.h>
#include <LibWeb/CSS/SelectorEngine.h>
#include <LibWeb/CSS/StyleResolver.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/DocumentType.h>
@ -43,10 +44,12 @@
#include <LibWeb/DOM/HTMLHeadElement.h>
#include <LibWeb/DOM/HTMLHtmlElement.h>
#include <LibWeb/DOM/HTMLTitleElement.h>
#include <LibWeb/Dump.h>
#include <LibWeb/Frame.h>
#include <LibWeb/HtmlView.h>
#include <LibWeb/Layout/LayoutDocument.h>
#include <LibWeb/Layout/LayoutTreeBuilder.h>
#include <LibWeb/Parser/CSSParser.h>
#include <stdio.h>
namespace Web {
@ -306,6 +309,23 @@ Vector<const Element*> Document::get_elements_by_name(const String& name) const
return elements;
}
NonnullRefPtrVector<Element> Document::query_selector_all(const StringView& selector_text)
{
auto selector = parse_selector(selector_text);
if (!selector.has_value())
return {};
NonnullRefPtrVector<Element> elements;
for_each_in_subtree_of_type<Element>([&](auto& element) {
if (SelectorEngine::matches(selector.value(), element)) {
elements.append(element);
}
return IterationDecision::Continue;
});
return elements;
}
Color Document::link_color() const
{
if (m_link_color.has_value())

View File

@ -123,6 +123,7 @@ public:
void schedule_style_update();
Vector<const Element*> get_elements_by_name(const String&) const;
NonnullRefPtrVector<Element> query_selector_all(const StringView&);
const String& source() const { return m_source; }
void set_source(const String& source) { m_source = source; }

View File

@ -170,78 +170,83 @@ void dump_tree(const LayoutNode& layout_node)
--indent;
}
void dump_selector(const Selector& selector)
{
dbgprintf(" Selector:\n");
for (auto& complex_selector : selector.complex_selectors()) {
dbgprintf(" ");
const char* relation_description = "";
switch (complex_selector.relation) {
case Selector::ComplexSelector::Relation::None:
break;
case Selector::ComplexSelector::Relation::ImmediateChild:
relation_description = "ImmediateChild";
break;
case Selector::ComplexSelector::Relation::Descendant:
relation_description = "Descendant";
break;
case Selector::ComplexSelector::Relation::AdjacentSibling:
relation_description = "AdjacentSibling";
break;
case Selector::ComplexSelector::Relation::GeneralSibling:
relation_description = "GeneralSibling";
break;
}
if (*relation_description)
dbgprintf("{%s} ", relation_description);
for (size_t i = 0; i < complex_selector.compound_selector.size(); ++i) {
auto& simple_selector = complex_selector.compound_selector[i];
const char* type_description = "Unknown";
switch (simple_selector.type) {
case Selector::SimpleSelector::Type::Invalid:
type_description = "Invalid";
break;
case Selector::SimpleSelector::Type::Universal:
type_description = "Universal";
break;
case Selector::SimpleSelector::Type::Id:
type_description = "Id";
break;
case Selector::SimpleSelector::Type::Class:
type_description = "Class";
break;
case Selector::SimpleSelector::Type::TagName:
type_description = "TagName";
break;
}
const char* attribute_match_type_description = "";
switch (simple_selector.attribute_match_type) {
case Selector::SimpleSelector::AttributeMatchType::None:
break;
case Selector::SimpleSelector::AttributeMatchType::HasAttribute:
attribute_match_type_description = "HasAttribute";
break;
case Selector::SimpleSelector::AttributeMatchType::ExactValueMatch:
attribute_match_type_description = "ExactValueMatch";
break;
}
dbgprintf("%s:%s", type_description, simple_selector.value.characters());
if (simple_selector.attribute_match_type != Selector::SimpleSelector::AttributeMatchType::None) {
dbgprintf(" [%s, name='%s', value='%s']", attribute_match_type_description, simple_selector.attribute_name.characters(), simple_selector.attribute_value.characters());
}
if (i != complex_selector.compound_selector.size() - 1)
dbgprintf(", ");
}
dbgprintf("\n");
}
}
void dump_rule(const StyleRule& rule)
{
dbgprintf("Rule:\n");
for (auto& selector : rule.selectors()) {
dbgprintf(" Selector:\n");
for (auto& complex_selector : selector.complex_selectors()) {
dbgprintf(" ");
const char* relation_description = "";
switch (complex_selector.relation) {
case Selector::ComplexSelector::Relation::None:
break;
case Selector::ComplexSelector::Relation::ImmediateChild:
relation_description = "ImmediateChild";
break;
case Selector::ComplexSelector::Relation::Descendant:
relation_description = "Descendant";
break;
case Selector::ComplexSelector::Relation::AdjacentSibling:
relation_description = "AdjacentSibling";
break;
case Selector::ComplexSelector::Relation::GeneralSibling:
relation_description = "GeneralSibling";
break;
}
if (*relation_description)
dbgprintf("{%s} ", relation_description);
for (size_t i = 0; i < complex_selector.compound_selector.size(); ++i) {
auto& simple_selector = complex_selector.compound_selector[i];
const char* type_description = "Unknown";
switch (simple_selector.type) {
case Selector::SimpleSelector::Type::Invalid:
type_description = "Invalid";
break;
case Selector::SimpleSelector::Type::Universal:
type_description = "Universal";
break;
case Selector::SimpleSelector::Type::Id:
type_description = "Id";
break;
case Selector::SimpleSelector::Type::Class:
type_description = "Class";
break;
case Selector::SimpleSelector::Type::TagName:
type_description = "TagName";
break;
}
const char* attribute_match_type_description = "";
switch (simple_selector.attribute_match_type) {
case Selector::SimpleSelector::AttributeMatchType::None:
break;
case Selector::SimpleSelector::AttributeMatchType::HasAttribute:
attribute_match_type_description = "HasAttribute";
break;
case Selector::SimpleSelector::AttributeMatchType::ExactValueMatch:
attribute_match_type_description = "ExactValueMatch";
break;
}
dbgprintf("%s:%s", type_description, simple_selector.value.characters());
if (simple_selector.attribute_match_type != Selector::SimpleSelector::AttributeMatchType::None) {
dbgprintf(" [%s, name='%s', value='%s']", attribute_match_type_description, simple_selector.attribute_name.characters(), simple_selector.attribute_value.characters());
}
if (i != complex_selector.compound_selector.size() - 1)
dbgprintf(", ");
}
dbgprintf("\n");
}
dump_selector(selector);
}
dbgprintf(" Declarations:\n");
for (auto& property : rule.declaration().properties()) {

View File

@ -26,17 +26,15 @@
#pragma once
namespace Web {
#include <LibWeb/Forward.h>
class Node;
class LayoutNode;
class StyleRule;
class StyleSheet;
namespace Web {
void dump_tree(const Node&);
void dump_tree(const LayoutNode&);
void dump_sheet(const StyleSheet&);
void dump_rule(const StyleRule&);
void dump_selector(const Selector&);
#undef HTML_DEBUG

View File

@ -35,11 +35,15 @@ class Event;
class EventListener;
class EventTarget;
class Frame;
class HTMLElement;
class HTMLCanvasElement;
class HTMLElement;
class HtmlView;
class LayoutNode;
class MouseEvent;
class Node;
class Selector;
class StyleRule;
class StyleSheet;
namespace Bindings {

View File

@ -267,6 +267,9 @@ public:
Optional<Selector::SimpleSelector> parse_simple_selector()
{
if (!peek())
return {};
if (consume_whitespace_or_comments())
return {};
@ -658,6 +661,16 @@ private:
StringView css;
};
Optional<Selector> parse_selector(const StringView& selector_text)
{
CSSParser parser(selector_text);
auto complex_selector = parser.parse_complex_selector();
if (!complex_selector.has_value())
return {};
complex_selector.value().relation = Selector::ComplexSelector::Relation::None;
return Selector({ complex_selector.value() });
}
RefPtr<StyleSheet> parse_css(const StringView& css)
{
CSSParser parser(css);

View File

@ -34,6 +34,7 @@ namespace Web {
RefPtr<StyleSheet> parse_css(const StringView&);
RefPtr<StyleDeclaration> parse_css_declaration(const StringView&);
NonnullRefPtr<StyleValue> parse_css_value(const StringView&);
Optional<Selector> parse_selector(const StringView&);
RefPtr<StyleValue> parse_line_width(const StringView&);
RefPtr<StyleValue> parse_color(const StringView&);