Browse Source

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.
Andreas Kling 5 năm trước cách đây
mục cha
commit
0f7bcd4111

+ 29 - 0
Base/home/anon/www/qsa.html

@@ -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>

+ 1 - 0
Base/home/anon/www/welcome.html

@@ -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>

+ 35 - 4
Libraries/LibWeb/Bindings/DocumentWrapper.cpp

@@ -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;
+}
+
 }
 }

+ 1 - 0
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&);
 };
 
 }

+ 20 - 0
Libraries/LibWeb/DOM/Document.cpp

@@ -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())

+ 1 - 0
Libraries/LibWeb/DOM/Document.h

@@ -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; }

+ 65 - 60
Libraries/LibWeb/Dump.cpp

@@ -170,78 +170,83 @@ void dump_tree(const LayoutNode& layout_node)
     --indent;
 }
 
-void dump_rule(const StyleRule& rule)
+void dump_selector(const Selector& selector)
 {
-    dbgprintf("Rule:\n");
-    for (auto& selector : rule.selectors()) {
-        dbgprintf("  Selector:\n");
+    dbgprintf("  Selector:\n");
 
-        for (auto& complex_selector : selector.complex_selectors()) {
-            dbgprintf("    ");
+    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;
+        }
 
-            const char* relation_description = "";
-            switch (complex_selector.relation) {
-            case Selector::ComplexSelector::Relation::None:
+        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::ComplexSelector::Relation::ImmediateChild:
-                relation_description = "ImmediateChild";
+            case Selector::SimpleSelector::Type::Universal:
+                type_description = "Universal";
                 break;
-            case Selector::ComplexSelector::Relation::Descendant:
-                relation_description = "Descendant";
+            case Selector::SimpleSelector::Type::Id:
+                type_description = "Id";
                 break;
-            case Selector::ComplexSelector::Relation::AdjacentSibling:
-                relation_description = "AdjacentSibling";
+            case Selector::SimpleSelector::Type::Class:
+                type_description = "Class";
                 break;
-            case Selector::ComplexSelector::Relation::GeneralSibling:
-                relation_description = "GeneralSibling";
+            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;
             }
 
-            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("%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());
             }
-            dbgprintf("\n");
+
+            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()) {
+        dump_selector(selector);
     }
     dbgprintf("  Declarations:\n");
     for (auto& property : rule.declaration().properties()) {

+ 3 - 5
Libraries/LibWeb/Dump.h

@@ -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
 

+ 5 - 1
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 {
 

+ 13 - 0
Libraries/LibWeb/Parser/CSSParser.cpp

@@ -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);

+ 1 - 0
Libraries/LibWeb/Parser/CSSParser.h

@@ -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&);