mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 23:50:19 +00:00
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.
This commit is contained in:
parent
49f3d88baf
commit
e4df1b223f
Notes:
sideshowbarker
2024-07-18 19:14:11 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/e4df1b223ff
14 changed files with 207 additions and 55 deletions
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/ScopeGuard.h>
|
||||
#include <LibWeb/Bindings/HTMLCollectionWrapper.h>
|
||||
#include <LibWeb/Bindings/NodeWrapper.h>
|
||||
#include <LibWeb/Bindings/NodeWrapperFactory.h>
|
||||
#include <LibWeb/DOM/Element.h>
|
||||
|
||||
namespace Web::Bindings {
|
||||
|
||||
JS::Value HTMLCollectionWrapper::get(JS::PropertyName const& name, JS::Value receiver, bool without_side_effects) const
|
||||
{
|
||||
auto* item = const_cast<DOM::HTMLCollection&>(impl()).named_item(name.to_string());
|
||||
if (!item)
|
||||
return Base::get(name, receiver, without_side_effects);
|
||||
return JS::Value { wrap(global_object(), *item) };
|
||||
}
|
||||
|
||||
JS::Value HTMLCollectionWrapper::get_by_index(u32 property_index) const
|
||||
{
|
||||
auto* item = const_cast<DOM::HTMLCollection&>(impl()).item(property_index);
|
||||
if (!item)
|
||||
return Base::get_by_index(property_index);
|
||||
return wrap(global_object(), *item);
|
||||
}
|
||||
|
||||
}
|
|
@ -3,6 +3,7 @@ set(SOURCES
|
|||
Bindings/EventListenerWrapper.cpp
|
||||
Bindings/EventWrapperFactory.cpp
|
||||
Bindings/EventTargetWrapperFactory.cpp
|
||||
Bindings/HTMLCollectionWrapperCustom.cpp
|
||||
Bindings/ImageConstructor.cpp
|
||||
Bindings/LocationObject.cpp
|
||||
Bindings/MainThreadVM.cpp
|
||||
|
@ -48,6 +49,7 @@ set(SOURCES
|
|||
DOM/Element.cpp
|
||||
DOM/ElementFactory.cpp
|
||||
DOM/Event.cpp
|
||||
DOM/HTMLCollection.cpp
|
||||
DOM/Range.cpp
|
||||
DOM/EventDispatcher.cpp
|
||||
DOM/EventListener.cpp
|
||||
|
@ -309,6 +311,7 @@ libweb_js_wrapper(DOM/DOMImplementation)
|
|||
libweb_js_wrapper(DOM/Element)
|
||||
libweb_js_wrapper(DOM/Event)
|
||||
libweb_js_wrapper(DOM/EventTarget)
|
||||
libweb_js_wrapper(DOM/HTMLCollection)
|
||||
libweb_js_wrapper(DOM/ProcessingInstruction)
|
||||
libweb_js_wrapper(DOM/ShadowRoot)
|
||||
libweb_js_wrapper(DOM/Node)
|
||||
|
|
|
@ -850,6 +850,7 @@ void generate_implementation(const IDL::Interface& interface)
|
|||
#include <LibWeb/Bindings/EventTargetWrapperFactory.h>
|
||||
#include <LibWeb/Bindings/EventWrapperFactory.h>
|
||||
#include <LibWeb/Bindings/HTMLCanvasElementWrapper.h>
|
||||
#include <LibWeb/Bindings/HTMLCollectionWrapper.h>
|
||||
#include <LibWeb/Bindings/HTMLFormElementWrapper.h>
|
||||
#include <LibWeb/Bindings/HTMLHeadElementWrapper.h>
|
||||
#include <LibWeb/Bindings/HTMLImageElementWrapper.h>
|
||||
|
@ -1191,6 +1192,7 @@ void generate_prototype_implementation(const IDL::Interface& interface)
|
|||
#include <LibWeb/Bindings/EventWrapperFactory.h>
|
||||
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
||||
#include <LibWeb/Bindings/HTMLCanvasElementWrapper.h>
|
||||
#include <LibWeb/Bindings/HTMLCollectionWrapper.h>
|
||||
#include <LibWeb/Bindings/HTMLFormElementWrapper.h>
|
||||
#include <LibWeb/Bindings/HTMLHeadElementWrapper.h>
|
||||
#include <LibWeb/Bindings/HTMLImageElementWrapper.h>
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <LibWeb/DOM/ElementFactory.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
#include <LibWeb/DOM/ExceptionOr.h>
|
||||
#include <LibWeb/DOM/HTMLCollection.h>
|
||||
#include <LibWeb/DOM/Range.h>
|
||||
#include <LibWeb/DOM/ShadowRoot.h>
|
||||
#include <LibWeb/DOM/Text.h>
|
||||
|
@ -484,42 +485,29 @@ void Document::set_hovered_node(Node* node)
|
|||
invalidate_style();
|
||||
}
|
||||
|
||||
NonnullRefPtrVector<Element> Document::get_elements_by_name(const String& name) const
|
||||
NonnullRefPtr<HTMLCollection> Document::get_elements_by_name(String const& name)
|
||||
{
|
||||
NonnullRefPtrVector<Element> elements;
|
||||
for_each_in_inclusive_subtree_of_type<Element>([&](auto& element) {
|
||||
if (element.attribute(HTML::AttributeNames::name) == name)
|
||||
elements.append(element);
|
||||
return IterationDecision::Continue;
|
||||
return HTMLCollection::create(*this, [name](Element const& element) {
|
||||
return element.name() == name;
|
||||
});
|
||||
return elements;
|
||||
}
|
||||
|
||||
NonnullRefPtrVector<Element> Document::get_elements_by_tag_name(const FlyString& tag_name) const
|
||||
NonnullRefPtr<HTMLCollection> Document::get_elements_by_tag_name(FlyString const& tag_name)
|
||||
{
|
||||
// FIXME: Support "*" for tag_name
|
||||
// https://dom.spec.whatwg.org/#concept-getelementsbytagname
|
||||
NonnullRefPtrVector<Element> elements;
|
||||
for_each_in_inclusive_subtree_of_type<Element>([&](auto& element) {
|
||||
if (element.namespace_() == Namespace::HTML
|
||||
? element.local_name().to_lowercase() == tag_name.to_lowercase()
|
||||
: element.local_name() == tag_name) {
|
||||
elements.append(element);
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
return HTMLCollection::create(*this, [tag_name](Element const& element) {
|
||||
if (element.namespace_() == Namespace::HTML)
|
||||
return element.local_name().to_lowercase() == tag_name.to_lowercase();
|
||||
return element.local_name() == tag_name;
|
||||
});
|
||||
return elements;
|
||||
}
|
||||
|
||||
NonnullRefPtrVector<Element> Document::get_elements_by_class_name(const FlyString& class_name) const
|
||||
NonnullRefPtr<HTMLCollection> Document::get_elements_by_class_name(FlyString const& class_name)
|
||||
{
|
||||
NonnullRefPtrVector<Element> elements;
|
||||
for_each_in_inclusive_subtree_of_type<Element>([&](auto& element) {
|
||||
if (element.has_class(class_name, in_quirks_mode() ? CaseSensitivity::CaseInsensitive : CaseSensitivity::CaseSensitive))
|
||||
elements.append(element);
|
||||
return IterationDecision::Continue;
|
||||
return HTMLCollection::create(*this, [class_name, quirks_mode = document().in_quirks_mode()](Element const& element) {
|
||||
return element.has_class(class_name, quirks_mode ? CaseSensitivity::CaseInsensitive : CaseSensitivity::CaseSensitive);
|
||||
});
|
||||
return elements;
|
||||
}
|
||||
|
||||
Color Document::link_color() const
|
||||
|
|
|
@ -136,9 +136,9 @@ public:
|
|||
void schedule_style_update();
|
||||
void schedule_forced_layout();
|
||||
|
||||
NonnullRefPtrVector<Element> get_elements_by_name(const String&) const;
|
||||
NonnullRefPtrVector<Element> get_elements_by_tag_name(const FlyString&) const;
|
||||
NonnullRefPtrVector<Element> get_elements_by_class_name(const FlyString&) const;
|
||||
NonnullRefPtr<HTMLCollection> get_elements_by_name(String const&);
|
||||
NonnullRefPtr<HTMLCollection> get_elements_by_tag_name(FlyString const&);
|
||||
NonnullRefPtr<HTMLCollection> get_elements_by_class_name(FlyString const&);
|
||||
|
||||
const String& source() const { return m_source; }
|
||||
void set_source(const String& source) { m_source = source; }
|
||||
|
|
|
@ -14,9 +14,9 @@ interface Document : Node {
|
|||
attribute DOMString cookie;
|
||||
|
||||
Element? getElementById(DOMString id);
|
||||
ArrayFromVector getElementsByName(DOMString name);
|
||||
ArrayFromVector getElementsByTagName(DOMString tagName);
|
||||
ArrayFromVector getElementsByClassName(DOMString className);
|
||||
HTMLCollection getElementsByName(DOMString name);
|
||||
HTMLCollection getElementsByTagName(DOMString tagName);
|
||||
HTMLCollection getElementsByClassName(DOMString className);
|
||||
|
||||
Element createElement(DOMString tagName);
|
||||
Element createElementNS(DOMString? namespace, DOMString qualifiedName);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -14,6 +14,7 @@
|
|||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/Element.h>
|
||||
#include <LibWeb/DOM/ExceptionOr.h>
|
||||
#include <LibWeb/DOM/HTMLCollection.h>
|
||||
#include <LibWeb/DOM/ShadowRoot.h>
|
||||
#include <LibWeb/DOM/Text.h>
|
||||
#include <LibWeb/HTML/Parser/HTMLDocumentParser.h>
|
||||
|
@ -325,31 +326,22 @@ bool Element::is_focused() const
|
|||
return document().focused_element() == this;
|
||||
}
|
||||
|
||||
NonnullRefPtrVector<Element> Element::get_elements_by_tag_name(const FlyString& tag_name) const
|
||||
NonnullRefPtr<HTMLCollection> Element::get_elements_by_tag_name(FlyString const& tag_name)
|
||||
{
|
||||
// FIXME: Support "*" for tag_name
|
||||
// https://dom.spec.whatwg.org/#concept-getelementsbytagname
|
||||
NonnullRefPtrVector<Element> elements;
|
||||
for_each_in_inclusive_subtree_of_type<Element>([&](auto& element) {
|
||||
if (element.namespace_() == Namespace::HTML
|
||||
? element.local_name().to_lowercase() == tag_name.to_lowercase()
|
||||
: element.local_name() == tag_name) {
|
||||
elements.append(element);
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
return HTMLCollection::create(*this, [tag_name](Element const& element) {
|
||||
if (element.namespace_() == Namespace::HTML)
|
||||
return element.local_name().to_lowercase() == tag_name.to_lowercase();
|
||||
return element.local_name() == tag_name;
|
||||
});
|
||||
return elements;
|
||||
}
|
||||
|
||||
NonnullRefPtrVector<Element> Element::get_elements_by_class_name(const FlyString& class_name) const
|
||||
NonnullRefPtr<HTMLCollection> Element::get_elements_by_class_name(FlyString const& class_name)
|
||||
{
|
||||
NonnullRefPtrVector<Element> elements;
|
||||
for_each_in_inclusive_subtree_of_type<Element>([&](auto& element) {
|
||||
if (element.has_class(class_name, m_document->in_quirks_mode() ? CaseSensitivity::CaseInsensitive : CaseSensitivity::CaseSensitive))
|
||||
elements.append(element);
|
||||
return IterationDecision::Continue;
|
||||
return HTMLCollection::create(*this, [class_name, quirks_mode = document().in_quirks_mode()](Element const& element) {
|
||||
return element.has_class(class_name, quirks_mode ? CaseSensitivity::CaseInsensitive : CaseSensitivity::CaseSensitive);
|
||||
});
|
||||
return elements;
|
||||
}
|
||||
|
||||
void Element::set_shadow_root(RefPtr<ShadowRoot> shadow_root)
|
||||
|
|
|
@ -82,8 +82,8 @@ public:
|
|||
bool is_focused() const;
|
||||
virtual bool is_focusable() const { return false; }
|
||||
|
||||
NonnullRefPtrVector<Element> get_elements_by_tag_name(const FlyString&) const;
|
||||
NonnullRefPtrVector<Element> get_elements_by_class_name(const FlyString&) const;
|
||||
NonnullRefPtr<HTMLCollection> get_elements_by_tag_name(FlyString const&);
|
||||
NonnullRefPtr<HTMLCollection> get_elements_by_class_name(FlyString const&);
|
||||
|
||||
ShadowRoot* shadow_root() { return m_shadow_root; }
|
||||
const ShadowRoot* shadow_root() const { return m_shadow_root; }
|
||||
|
|
|
@ -8,8 +8,8 @@ interface Element : Node {
|
|||
boolean hasAttribute(DOMString qualifiedName);
|
||||
boolean hasAttributes();
|
||||
|
||||
ArrayFromVector getElementsByTagName(DOMString tagName);
|
||||
ArrayFromVector getElementsByClassName(DOMString className);
|
||||
HTMLCollection getElementsByTagName(DOMString tagName);
|
||||
HTMLCollection getElementsByClassName(DOMString className);
|
||||
|
||||
[LegacyNullToEmptyString] attribute DOMString innerHTML;
|
||||
[Reflect] attribute DOMString id;
|
||||
|
|
61
Userland/Libraries/LibWeb/DOM/HTMLCollection.cpp
Normal file
61
Userland/Libraries/LibWeb/DOM/HTMLCollection.cpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/DOM/Element.h>
|
||||
#include <LibWeb/DOM/HTMLCollection.h>
|
||||
#include <LibWeb/DOM/ParentNode.h>
|
||||
|
||||
namespace Web::DOM {
|
||||
|
||||
HTMLCollection::HTMLCollection(ParentNode& root, Function<bool(Element const&)> filter)
|
||||
: m_root(root)
|
||||
, m_filter(move(filter))
|
||||
{
|
||||
}
|
||||
|
||||
HTMLCollection::~HTMLCollection()
|
||||
{
|
||||
}
|
||||
|
||||
Vector<NonnullRefPtr<Element>> HTMLCollection::collect_matching_elements()
|
||||
{
|
||||
Vector<NonnullRefPtr<Element>> elements;
|
||||
m_root->for_each_in_inclusive_subtree_of_type<Element>([&](auto& element) {
|
||||
if (m_filter(element))
|
||||
elements.append(element);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
return elements;
|
||||
}
|
||||
|
||||
size_t HTMLCollection::length()
|
||||
{
|
||||
return collect_matching_elements().size();
|
||||
}
|
||||
|
||||
Element* HTMLCollection::item(size_t index)
|
||||
{
|
||||
auto elements = collect_matching_elements();
|
||||
if (index >= elements.size())
|
||||
return nullptr;
|
||||
return elements[index];
|
||||
}
|
||||
|
||||
Element* HTMLCollection::named_item(FlyString const& name)
|
||||
{
|
||||
if (name.is_null())
|
||||
return nullptr;
|
||||
auto elements = collect_matching_elements();
|
||||
// First look for an "id" attribute match
|
||||
if (auto it = elements.find_if([&](auto& entry) { return entry->attribute(HTML::AttributeNames::id) == name; }); it != elements.end())
|
||||
return *it;
|
||||
// Then look for a "name" attribute match
|
||||
if (auto it = elements.find_if([&](auto& entry) { return entry->name() == name; }); it != elements.end())
|
||||
return *it;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
64
Userland/Libraries/LibWeb/DOM/HTMLCollection.h
Normal file
64
Userland/Libraries/LibWeb/DOM/HTMLCollection.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/FlyString.h>
|
||||
#include <AK/Function.h>
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <LibWeb/Bindings/Wrappable.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
namespace Web::DOM {
|
||||
|
||||
// NOTE: HTMLCollection is in the DOM namespace because it's part of the DOM specification.
|
||||
|
||||
// This class implements a live, filtered view of a DOM subtree.
|
||||
// When constructing an HTMLCollection, you provide a root node + a filter.
|
||||
// The filter is a simple Function object that answers the question
|
||||
// "is this Element part of the collection?"
|
||||
|
||||
// FIXME: HTMLCollection currently does no caching. It will re-filter on every access!
|
||||
// We should teach it how to cache results. The main challenge is invalidating
|
||||
// these caches, since this needs to happen on various kinds of DOM mutation.
|
||||
|
||||
class HTMLCollection
|
||||
: public RefCounted<HTMLCollection>
|
||||
, public Bindings::Wrappable {
|
||||
AK_MAKE_NONCOPYABLE(HTMLCollection);
|
||||
AK_MAKE_NONMOVABLE(HTMLCollection);
|
||||
|
||||
public:
|
||||
using WrapperType = Bindings::HTMLCollectionWrapper;
|
||||
|
||||
static NonnullRefPtr<HTMLCollection> create(ParentNode& root, Function<bool(Element const&)> filter)
|
||||
{
|
||||
return adopt(*new HTMLCollection(root, move(filter)));
|
||||
}
|
||||
|
||||
~HTMLCollection();
|
||||
|
||||
size_t length();
|
||||
Element* item(size_t index);
|
||||
Element* named_item(FlyString const& name);
|
||||
|
||||
Vector<NonnullRefPtr<Element>> collect_matching_elements();
|
||||
|
||||
protected:
|
||||
HTMLCollection(ParentNode& root, Function<bool(Element const&)> filter);
|
||||
|
||||
private:
|
||||
NonnullRefPtr<ParentNode> m_root;
|
||||
Function<bool(Element const&)> m_filter;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace Web::Bindings {
|
||||
|
||||
HTMLCollectionWrapper* wrap(JS::GlobalObject&, DOM::HTMLCollection&);
|
||||
|
||||
}
|
8
Userland/Libraries/LibWeb/DOM/HTMLCollection.idl
Normal file
8
Userland/Libraries/LibWeb/DOM/HTMLCollection.idl
Normal file
|
@ -0,0 +1,8 @@
|
|||
[CustomGet,CustomGetByIndex]
|
||||
interface HTMLCollection {
|
||||
|
||||
readonly attribute unsigned long length;
|
||||
Element? item(unsigned long index);
|
||||
Element? namedItem(DOMString name);
|
||||
|
||||
};
|
|
@ -42,6 +42,7 @@ class Event;
|
|||
class EventHandler;
|
||||
class EventListener;
|
||||
class EventTarget;
|
||||
class HTMLCollection;
|
||||
class MouseEvent;
|
||||
class Node;
|
||||
class ParentNode;
|
||||
|
@ -218,6 +219,7 @@ class HTMLBodyElementWrapper;
|
|||
class HTMLBRElementWrapper;
|
||||
class HTMLButtonElementWrapper;
|
||||
class HTMLCanvasElementWrapper;
|
||||
class HTMLCollectionWrapper;
|
||||
class HTMLDataElementWrapper;
|
||||
class HTMLDataListElementWrapper;
|
||||
class HTMLDetailsElementWrapper;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
#include <LibWeb/DOM/HTMLCollection.h>
|
||||
#include <LibWeb/DOM/Window.h>
|
||||
#include <LibWeb/HTML/HTMLAnchorElement.h>
|
||||
#include <LibWeb/InProcessWebView.h>
|
||||
|
@ -161,9 +162,9 @@ void Frame::scroll_to_anchor(const String& fragment)
|
|||
auto element = document()->get_element_by_id(fragment);
|
||||
if (!element) {
|
||||
auto candidates = document()->get_elements_by_name(fragment);
|
||||
for (auto& candidate : candidates) {
|
||||
if (is<HTML::HTMLAnchorElement>(candidate)) {
|
||||
element = downcast<HTML::HTMLAnchorElement>(candidate);
|
||||
for (auto& candidate : candidates->collect_matching_elements()) {
|
||||
if (is<HTML::HTMLAnchorElement>(*candidate)) {
|
||||
element = downcast<HTML::HTMLAnchorElement>(*candidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue