mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-09 18:16:09 +03:00
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:
parent
c56acba75e
commit
0f7bcd4111
Notes:
sideshowbarker
2024-07-19 08:03:42 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/0f7bcd4111b
29
Base/home/anon/www/qsa.html
Normal file
29
Base/home/anon/www/qsa.html
Normal 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>
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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&);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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; }
|
||||
|
@ -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()) {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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&);
|
||||
|
Loading…
Reference in New Issue
Block a user