Skip to content

Commit e4df1b2

Browse files
committed
LibWeb: Implement a slow but functional HTMLCollection :^)
HTMLCollection is an awkward legacy interface from the DOM spec. It provides a live view of a DOM subtree, with some kind of filtering that determines which elements are part of the collection. We now return HTMLCollection objects from these APIs: - getElementsByClassName() - getElementsByName() - getElementsByTagName() This initial implementation does not do any kind of caching, since that is quite a tricky problem, and there will be plenty of time for tricky problems later on when the engine is more mature.
1 parent 49f3d88 commit e4df1b2

File tree

14 files changed

+207
-55
lines changed

14 files changed

+207
-55
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include <AK/ScopeGuard.h>
8+
#include <LibWeb/Bindings/HTMLCollectionWrapper.h>
9+
#include <LibWeb/Bindings/NodeWrapper.h>
10+
#include <LibWeb/Bindings/NodeWrapperFactory.h>
11+
#include <LibWeb/DOM/Element.h>
12+
13+
namespace Web::Bindings {
14+
15+
JS::Value HTMLCollectionWrapper::get(JS::PropertyName const& name, JS::Value receiver, bool without_side_effects) const
16+
{
17+
auto* item = const_cast<DOM::HTMLCollection&>(impl()).named_item(name.to_string());
18+
if (!item)
19+
return Base::get(name, receiver, without_side_effects);
20+
return JS::Value { wrap(global_object(), *item) };
21+
}
22+
23+
JS::Value HTMLCollectionWrapper::get_by_index(u32 property_index) const
24+
{
25+
auto* item = const_cast<DOM::HTMLCollection&>(impl()).item(property_index);
26+
if (!item)
27+
return Base::get_by_index(property_index);
28+
return wrap(global_object(), *item);
29+
}
30+
31+
}

Userland/Libraries/LibWeb/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ set(SOURCES
33
Bindings/EventListenerWrapper.cpp
44
Bindings/EventWrapperFactory.cpp
55
Bindings/EventTargetWrapperFactory.cpp
6+
Bindings/HTMLCollectionWrapperCustom.cpp
67
Bindings/ImageConstructor.cpp
78
Bindings/LocationObject.cpp
89
Bindings/MainThreadVM.cpp
@@ -48,6 +49,7 @@ set(SOURCES
4849
DOM/Element.cpp
4950
DOM/ElementFactory.cpp
5051
DOM/Event.cpp
52+
DOM/HTMLCollection.cpp
5153
DOM/Range.cpp
5254
DOM/EventDispatcher.cpp
5355
DOM/EventListener.cpp
@@ -309,6 +311,7 @@ libweb_js_wrapper(DOM/DOMImplementation)
309311
libweb_js_wrapper(DOM/Element)
310312
libweb_js_wrapper(DOM/Event)
311313
libweb_js_wrapper(DOM/EventTarget)
314+
libweb_js_wrapper(DOM/HTMLCollection)
312315
libweb_js_wrapper(DOM/ProcessingInstruction)
313316
libweb_js_wrapper(DOM/ShadowRoot)
314317
libweb_js_wrapper(DOM/Node)

Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,7 @@ void generate_implementation(const IDL::Interface& interface)
850850
#include <LibWeb/Bindings/EventTargetWrapperFactory.h>
851851
#include <LibWeb/Bindings/EventWrapperFactory.h>
852852
#include <LibWeb/Bindings/HTMLCanvasElementWrapper.h>
853+
#include <LibWeb/Bindings/HTMLCollectionWrapper.h>
853854
#include <LibWeb/Bindings/HTMLFormElementWrapper.h>
854855
#include <LibWeb/Bindings/HTMLHeadElementWrapper.h>
855856
#include <LibWeb/Bindings/HTMLImageElementWrapper.h>
@@ -1191,6 +1192,7 @@ void generate_prototype_implementation(const IDL::Interface& interface)
11911192
#include <LibWeb/Bindings/EventWrapperFactory.h>
11921193
#include <LibWeb/Bindings/ExceptionOrUtils.h>
11931194
#include <LibWeb/Bindings/HTMLCanvasElementWrapper.h>
1195+
#include <LibWeb/Bindings/HTMLCollectionWrapper.h>
11941196
#include <LibWeb/Bindings/HTMLFormElementWrapper.h>
11951197
#include <LibWeb/Bindings/HTMLHeadElementWrapper.h>
11961198
#include <LibWeb/Bindings/HTMLImageElementWrapper.h>

Userland/Libraries/LibWeb/DOM/Document.cpp

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <LibWeb/DOM/ElementFactory.h>
2525
#include <LibWeb/DOM/Event.h>
2626
#include <LibWeb/DOM/ExceptionOr.h>
27+
#include <LibWeb/DOM/HTMLCollection.h>
2728
#include <LibWeb/DOM/Range.h>
2829
#include <LibWeb/DOM/ShadowRoot.h>
2930
#include <LibWeb/DOM/Text.h>
@@ -484,42 +485,29 @@ void Document::set_hovered_node(Node* node)
484485
invalidate_style();
485486
}
486487

487-
NonnullRefPtrVector<Element> Document::get_elements_by_name(const String& name) const
488+
NonnullRefPtr<HTMLCollection> Document::get_elements_by_name(String const& name)
488489
{
489-
NonnullRefPtrVector<Element> elements;
490-
for_each_in_inclusive_subtree_of_type<Element>([&](auto& element) {
491-
if (element.attribute(HTML::AttributeNames::name) == name)
492-
elements.append(element);
493-
return IterationDecision::Continue;
490+
return HTMLCollection::create(*this, [name](Element const& element) {
491+
return element.name() == name;
494492
});
495-
return elements;
496493
}
497494

498-
NonnullRefPtrVector<Element> Document::get_elements_by_tag_name(const FlyString& tag_name) const
495+
NonnullRefPtr<HTMLCollection> Document::get_elements_by_tag_name(FlyString const& tag_name)
499496
{
500497
// FIXME: Support "*" for tag_name
501498
// https://dom.spec.whatwg.org/#concept-getelementsbytagname
502-
NonnullRefPtrVector<Element> elements;
503-
for_each_in_inclusive_subtree_of_type<Element>([&](auto& element) {
504-
if (element.namespace_() == Namespace::HTML
505-
? element.local_name().to_lowercase() == tag_name.to_lowercase()
506-
: element.local_name() == tag_name) {
507-
elements.append(element);
508-
}
509-
return IterationDecision::Continue;
499+
return HTMLCollection::create(*this, [tag_name](Element const& element) {
500+
if (element.namespace_() == Namespace::HTML)
501+
return element.local_name().to_lowercase() == tag_name.to_lowercase();
502+
return element.local_name() == tag_name;
510503
});
511-
return elements;
512504
}
513505

514-
NonnullRefPtrVector<Element> Document::get_elements_by_class_name(const FlyString& class_name) const
506+
NonnullRefPtr<HTMLCollection> Document::get_elements_by_class_name(FlyString const& class_name)
515507
{
516-
NonnullRefPtrVector<Element> elements;
517-
for_each_in_inclusive_subtree_of_type<Element>([&](auto& element) {
518-
if (element.has_class(class_name, in_quirks_mode() ? CaseSensitivity::CaseInsensitive : CaseSensitivity::CaseSensitive))
519-
elements.append(element);
520-
return IterationDecision::Continue;
508+
return HTMLCollection::create(*this, [class_name, quirks_mode = document().in_quirks_mode()](Element const& element) {
509+
return element.has_class(class_name, quirks_mode ? CaseSensitivity::CaseInsensitive : CaseSensitivity::CaseSensitive);
521510
});
522-
return elements;
523511
}
524512

525513
Color Document::link_color() const

Userland/Libraries/LibWeb/DOM/Document.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,9 @@ class Document
136136
void schedule_style_update();
137137
void schedule_forced_layout();
138138

139-
NonnullRefPtrVector<Element> get_elements_by_name(const String&) const;
140-
NonnullRefPtrVector<Element> get_elements_by_tag_name(const FlyString&) const;
141-
NonnullRefPtrVector<Element> get_elements_by_class_name(const FlyString&) const;
139+
NonnullRefPtr<HTMLCollection> get_elements_by_name(String const&);
140+
NonnullRefPtr<HTMLCollection> get_elements_by_tag_name(FlyString const&);
141+
NonnullRefPtr<HTMLCollection> get_elements_by_class_name(FlyString const&);
142142

143143
const String& source() const { return m_source; }
144144
void set_source(const String& source) { m_source = source; }

Userland/Libraries/LibWeb/DOM/Document.idl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ interface Document : Node {
1414
attribute DOMString cookie;
1515

1616
Element? getElementById(DOMString id);
17-
ArrayFromVector getElementsByName(DOMString name);
18-
ArrayFromVector getElementsByTagName(DOMString tagName);
19-
ArrayFromVector getElementsByClassName(DOMString className);
17+
HTMLCollection getElementsByName(DOMString name);
18+
HTMLCollection getElementsByTagName(DOMString tagName);
19+
HTMLCollection getElementsByClassName(DOMString className);
2020

2121
Element createElement(DOMString tagName);
2222
Element createElementNS(DOMString? namespace, DOMString qualifiedName);

Userland/Libraries/LibWeb/DOM/Element.cpp

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
2+
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
33
*
44
* SPDX-License-Identifier: BSD-2-Clause
55
*/
@@ -14,6 +14,7 @@
1414
#include <LibWeb/DOM/Document.h>
1515
#include <LibWeb/DOM/Element.h>
1616
#include <LibWeb/DOM/ExceptionOr.h>
17+
#include <LibWeb/DOM/HTMLCollection.h>
1718
#include <LibWeb/DOM/ShadowRoot.h>
1819
#include <LibWeb/DOM/Text.h>
1920
#include <LibWeb/HTML/Parser/HTMLDocumentParser.h>
@@ -325,31 +326,22 @@ bool Element::is_focused() const
325326
return document().focused_element() == this;
326327
}
327328

328-
NonnullRefPtrVector<Element> Element::get_elements_by_tag_name(const FlyString& tag_name) const
329+
NonnullRefPtr<HTMLCollection> Element::get_elements_by_tag_name(FlyString const& tag_name)
329330
{
330331
// FIXME: Support "*" for tag_name
331332
// https://dom.spec.whatwg.org/#concept-getelementsbytagname
332-
NonnullRefPtrVector<Element> elements;
333-
for_each_in_inclusive_subtree_of_type<Element>([&](auto& element) {
334-
if (element.namespace_() == Namespace::HTML
335-
? element.local_name().to_lowercase() == tag_name.to_lowercase()
336-
: element.local_name() == tag_name) {
337-
elements.append(element);
338-
}
339-
return IterationDecision::Continue;
333+
return HTMLCollection::create(*this, [tag_name](Element const& element) {
334+
if (element.namespace_() == Namespace::HTML)
335+
return element.local_name().to_lowercase() == tag_name.to_lowercase();
336+
return element.local_name() == tag_name;
340337
});
341-
return elements;
342338
}
343339

344-
NonnullRefPtrVector<Element> Element::get_elements_by_class_name(const FlyString& class_name) const
340+
NonnullRefPtr<HTMLCollection> Element::get_elements_by_class_name(FlyString const& class_name)
345341
{
346-
NonnullRefPtrVector<Element> elements;
347-
for_each_in_inclusive_subtree_of_type<Element>([&](auto& element) {
348-
if (element.has_class(class_name, m_document->in_quirks_mode() ? CaseSensitivity::CaseInsensitive : CaseSensitivity::CaseSensitive))
349-
elements.append(element);
350-
return IterationDecision::Continue;
342+
return HTMLCollection::create(*this, [class_name, quirks_mode = document().in_quirks_mode()](Element const& element) {
343+
return element.has_class(class_name, quirks_mode ? CaseSensitivity::CaseInsensitive : CaseSensitivity::CaseSensitive);
351344
});
352-
return elements;
353345
}
354346

355347
void Element::set_shadow_root(RefPtr<ShadowRoot> shadow_root)

Userland/Libraries/LibWeb/DOM/Element.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ class Element
8282
bool is_focused() const;
8383
virtual bool is_focusable() const { return false; }
8484

85-
NonnullRefPtrVector<Element> get_elements_by_tag_name(const FlyString&) const;
86-
NonnullRefPtrVector<Element> get_elements_by_class_name(const FlyString&) const;
85+
NonnullRefPtr<HTMLCollection> get_elements_by_tag_name(FlyString const&);
86+
NonnullRefPtr<HTMLCollection> get_elements_by_class_name(FlyString const&);
8787

8888
ShadowRoot* shadow_root() { return m_shadow_root; }
8989
const ShadowRoot* shadow_root() const { return m_shadow_root; }

Userland/Libraries/LibWeb/DOM/Element.idl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ interface Element : Node {
88
boolean hasAttribute(DOMString qualifiedName);
99
boolean hasAttributes();
1010

11-
ArrayFromVector getElementsByTagName(DOMString tagName);
12-
ArrayFromVector getElementsByClassName(DOMString className);
11+
HTMLCollection getElementsByTagName(DOMString tagName);
12+
HTMLCollection getElementsByClassName(DOMString className);
1313

1414
[LegacyNullToEmptyString] attribute DOMString innerHTML;
1515
[Reflect] attribute DOMString id;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include <LibWeb/DOM/Element.h>
8+
#include <LibWeb/DOM/HTMLCollection.h>
9+
#include <LibWeb/DOM/ParentNode.h>
10+
11+
namespace Web::DOM {
12+
13+
HTMLCollection::HTMLCollection(ParentNode& root, Function<bool(Element const&)> filter)
14+
: m_root(root)
15+
, m_filter(move(filter))
16+
{
17+
}
18+
19+
HTMLCollection::~HTMLCollection()
20+
{
21+
}
22+
23+
Vector<NonnullRefPtr<Element>> HTMLCollection::collect_matching_elements()
24+
{
25+
Vector<NonnullRefPtr<Element>> elements;
26+
m_root->for_each_in_inclusive_subtree_of_type<Element>([&](auto& element) {
27+
if (m_filter(element))
28+
elements.append(element);
29+
return IterationDecision::Continue;
30+
});
31+
return elements;
32+
}
33+
34+
size_t HTMLCollection::length()
35+
{
36+
return collect_matching_elements().size();
37+
}
38+
39+
Element* HTMLCollection::item(size_t index)
40+
{
41+
auto elements = collect_matching_elements();
42+
if (index >= elements.size())
43+
return nullptr;
44+
return elements[index];
45+
}
46+
47+
Element* HTMLCollection::named_item(FlyString const& name)
48+
{
49+
if (name.is_null())
50+
return nullptr;
51+
auto elements = collect_matching_elements();
52+
// First look for an "id" attribute match
53+
if (auto it = elements.find_if([&](auto& entry) { return entry->attribute(HTML::AttributeNames::id) == name; }); it != elements.end())
54+
return *it;
55+
// Then look for a "name" attribute match
56+
if (auto it = elements.find_if([&](auto& entry) { return entry->name() == name; }); it != elements.end())
57+
return *it;
58+
return nullptr;
59+
}
60+
61+
}

0 commit comments

Comments
 (0)