diff --git a/Base/home/anon/www/qsa.html b/Base/home/anon/www/qsa.html
new file mode 100644
index 0000000000..a0df99c14a
--- /dev/null
+++ b/Base/home/anon/www/qsa.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Base/home/anon/www/welcome.html b/Base/home/anon/www/welcome.html
index af8dd8381d..01a80d67f7 100644
--- a/Base/home/anon/www/welcome.html
+++ b/Base/home/anon/www/welcome.html
@@ -23,6 +23,7 @@ h1 {
This is a very simple browser built on the LibWeb engine.
Some small test pages:
+ - querySelectorAll test
- innerHTML property test
- position: absolute; for top and left
- fun demo
diff --git a/Libraries/LibWeb/Bindings/DocumentWrapper.cpp b/Libraries/LibWeb/Bindings/DocumentWrapper.cpp
index 1f0926c1f1..110c2986ea 100644
--- a/Libraries/LibWeb/Bindings/DocumentWrapper.cpp
+++ b/Libraries/LibWeb/Bindings/DocumentWrapper.cpp
@@ -26,6 +26,8 @@
#include
#include
+#include
+#include
#include
#include
#include
@@ -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(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(this_object)->node();
+ if (StringView("DocumentWrapper") != this_object->class_name()) {
+ interpreter.throw_exception("TypeError", "That's not a DocumentWrapper, bro.");
+ return {};
+ }
+ return &static_cast(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));
}
+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();
+ for (auto& element : elements) {
+ node_list->push(wrap(interpreter.heap(), element));
+ }
+ return node_list;
+}
+
}
}
diff --git a/Libraries/LibWeb/Bindings/DocumentWrapper.h b/Libraries/LibWeb/Bindings/DocumentWrapper.h
index dbde01798e..86f2270ae9 100644
--- a/Libraries/LibWeb/Bindings/DocumentWrapper.h
+++ b/Libraries/LibWeb/Bindings/DocumentWrapper.h
@@ -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&);
};
}
diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp
index e3520c2ad2..c1d2931d8e 100644
--- a/Libraries/LibWeb/DOM/Document.cpp
+++ b/Libraries/LibWeb/DOM/Document.cpp
@@ -34,6 +34,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -43,10 +44,12 @@
#include
#include
#include
+#include
#include
#include
#include
#include
+#include
#include
namespace Web {
@@ -306,6 +309,23 @@ Vector Document::get_elements_by_name(const String& name) const
return elements;
}
+NonnullRefPtrVector Document::query_selector_all(const StringView& selector_text)
+{
+ auto selector = parse_selector(selector_text);
+ if (!selector.has_value())
+ return {};
+
+ NonnullRefPtrVector elements;
+ for_each_in_subtree_of_type([&](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())
diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h
index dab8e4db0f..df183081a7 100644
--- a/Libraries/LibWeb/DOM/Document.h
+++ b/Libraries/LibWeb/DOM/Document.h
@@ -123,6 +123,7 @@ public:
void schedule_style_update();
Vector get_elements_by_name(const String&) const;
+ NonnullRefPtrVector query_selector_all(const StringView&);
const String& source() const { return m_source; }
void set_source(const String& source) { m_source = source; }
diff --git a/Libraries/LibWeb/Dump.cpp b/Libraries/LibWeb/Dump.cpp
index 781d5f48eb..da3ad428a7 100644
--- a/Libraries/LibWeb/Dump.cpp
+++ b/Libraries/LibWeb/Dump.cpp
@@ -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()) {
diff --git a/Libraries/LibWeb/Dump.h b/Libraries/LibWeb/Dump.h
index 07b5a7e17c..8108819761 100644
--- a/Libraries/LibWeb/Dump.h
+++ b/Libraries/LibWeb/Dump.h
@@ -26,17 +26,15 @@
#pragma once
-namespace Web {
+#include
-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
diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h
index c94ddd4400..0403db9b97 100644
--- a/Libraries/LibWeb/Forward.h
+++ b/Libraries/LibWeb/Forward.h
@@ -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 {
diff --git a/Libraries/LibWeb/Parser/CSSParser.cpp b/Libraries/LibWeb/Parser/CSSParser.cpp
index ff18109816..64b9c62e08 100644
--- a/Libraries/LibWeb/Parser/CSSParser.cpp
+++ b/Libraries/LibWeb/Parser/CSSParser.cpp
@@ -267,6 +267,9 @@ public:
Optional parse_simple_selector()
{
+ if (!peek())
+ return {};
+
if (consume_whitespace_or_comments())
return {};
@@ -658,6 +661,16 @@ private:
StringView css;
};
+Optional 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 parse_css(const StringView& css)
{
CSSParser parser(css);
diff --git a/Libraries/LibWeb/Parser/CSSParser.h b/Libraries/LibWeb/Parser/CSSParser.h
index 0a4555e5d9..f62d0891ef 100644
--- a/Libraries/LibWeb/Parser/CSSParser.h
+++ b/Libraries/LibWeb/Parser/CSSParser.h
@@ -34,6 +34,7 @@ namespace Web {
RefPtr parse_css(const StringView&);
RefPtr parse_css_declaration(const StringView&);
NonnullRefPtr parse_css_value(const StringView&);
+Optional parse_selector(const StringView&);
RefPtr parse_line_width(const StringView&);
RefPtr parse_color(const StringView&);